Docker para principiantes: imágenes, contenedores y tu primer Dockerfile

"En mi computador funciona." Esa frase aparece cada vez que un proyecto pasa de una máquina a otra y algo se rompe: otra versión de Python, una librería que falta, una variable del sistema distinta. Docker existe para cortar ese problema de raíz. En lugar de entregar solo tu código, entregas el código junto con el entorno exacto en el que debe ejecutarse.

En esta guía vas a construir una aplicación pequeña pero real, empaquetarla en una imagen, ejecutarla como contenedor y hacer que sus datos sobrevivan aunque borres el contenedor. Al terminar vas a entender los tres conceptos que más confunden al empezar: imagen, contenedor y volumen.
Antes de empezar: qué necesitas
Instala Docker Desktop si usas Windows o macOS, o Docker Engine si usas Linux. En Windows, Docker Desktop trabaja sobre WSL 2, así que el instalador te pedirá activarlo si todavía no lo tienes. Para comprobar que todo quedó bien, abre una terminal y ejecuta:
docker --version
docker run hello-worldEl segundo comando descarga una imagen mínima y la ejecuta. Si ves un mensaje de bienvenida que empieza con "Hello from Docker!", tu instalación funciona. Si recibes un error de permisos en Linux, lo más común es que tu usuario no pertenezca al grupo docker; la documentación oficial de Docker explica cómo agregarlo en la sección de pasos posteriores a la instalación.
Los tres conceptos, sin rodeos
Antes de tocar código conviene tener claro el vocabulario, porque casi todos los errores de principiante vienen de mezclar estos términos.
| Concepto | Qué es | Analogía útil |
|---|---|---|
| Imagen | Plantilla de solo lectura con el sistema base, dependencias y tu código | Una receta impresa: no cambia aunque cocines mil veces |
| Contenedor | Proceso en ejecución creado a partir de una imagen, con su propia capa de escritura | El plato que preparas con esa receta |
| Volumen | Almacenamiento gestionado por Docker que vive fuera del ciclo de vida del contenedor | La despensa: sigue ahí aunque tires el plato |
La idea clave es esta: un contenedor es desechable. Puedes borrarlo y crear otro idéntico en segundos a partir de la misma imagen. Todo lo que el contenedor haya escrito dentro de su propio sistema de archivos desaparece con él, a menos que lo guardes en un volumen.
Paso 1: la aplicación de ejemplo
Vamos a usar un contador de visitas escrito en Python con Flask. Es lo bastante simple para leerlo en un minuto, pero tiene algo importante: guarda un dato en disco. Eso nos permitirá ver con claridad qué pasa con los datos cuando el contenedor se elimina.
Crea una carpeta llamada contador y dentro de ella el archivo app.py:
from pathlib import Path
from flask import Flask
app = Flask(__name__)
# El contador se guarda en esta ruta dentro del contenedor
ARCHIVO = Path("/datos/visitas.txt")
@app.route("/")
def inicio():
ARCHIVO.parent.mkdir(parents=True, exist_ok=True)
visitas = int(ARCHIVO.read_text()) if ARCHIVO.exists() else 0
visitas += 1
ARCHIVO.write_text(str(visitas))
return f"Esta página se ha visitado {visitas} veces\n"Ahora el archivo requirements.txt con las dependencias. Fijar versiones exactas es una buena costumbre: garantiza que la imagen que construyas hoy sea igual a la que construyas dentro de seis meses.
flask==3.0.3
gunicorn==22.0.0Gunicorn es un servidor WSGI pensado para producción. El servidor que trae Flask sirve para desarrollar, pero no para atender tráfico real.
Paso 2: escribir el Dockerfile
El Dockerfile es la receta de tu imagen. Cada instrucción crea una capa, y Docker guarda esas capas en caché para no repetir trabajo en la siguiente construcción. Crea un archivo llamado exactamente Dockerfile, sin extensión:
# Imagen base oficial de Python, variante reducida
FROM python:3.12-slim
# Carpeta de trabajo dentro de la imagen
WORKDIR /app
# Primero solo las dependencias, para aprovechar la caché
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Después el resto del código
COPY . .
# Documenta el puerto en el que escucha la aplicación
EXPOSE 8000
# Comando que se ejecuta al iniciar el contenedor
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]Fíjate en el orden. Si copiaras todo el código antes de instalar dependencias, cualquier cambio mínimo en app.py invalidaría la caché y Docker reinstalaría Flask y Gunicorn en cada construcción. Copiando primero requirements.txt, la instalación solo se repite cuando cambian las dependencias.
Otro detalle: la aplicación escucha en 0.0.0.0 y no en 127.0.0.1. Dentro de un contenedor, 127.0.0.1 significa "solo este contenedor", así que las peticiones que llegan desde tu computador nunca alcanzarían la aplicación.
El archivo .dockerignore
Igual que .gitignore, el archivo .dockerignore le dice a Docker qué no debe copiar a la imagen. Evita imágenes pesadas y, más importante, evita que se cuelen archivos sensibles:
__pycache__/
*.pyc
.venv/
.env
.git/Paso 3: construir la imagen
Desde la carpeta contador, ejecuta:
docker build -t contador:1.0 .La opción -t le pone nombre y etiqueta a la imagen (contador es el nombre, 1.0 la etiqueta). El punto final indica el contexto de construcción: la carpeta cuyos archivos se envían a Docker. Si no pones etiqueta, Docker usa latest, que no significa "la más reciente", sino simplemente "la que no tiene otra etiqueta". Por eso conviene versionar de forma explícita.
Comprueba que la imagen existe con docker images. Verás su nombre, etiqueta, identificador y tamaño.
Paso 4: ejecutar el contenedor
docker run -d --name contador -p 8080:8000 contador:1.0Lo que hace cada opción:
-dejecuta el contenedor en segundo plano y te devuelve la terminal.--name contadorle da un nombre legible, para no depender del identificador aleatorio.-p 8080:8000publica el puerto: lo que llegue al puerto 8080 de tu computador se redirige al 8000 del contenedor. El orden siempre eshost:contenedor.
Abre http://localhost:8080 en el navegador o usa curl http://localhost:8080 varias veces. El número de visitas va subiendo. Para ver qué está pasando dentro, usa docker ps (contenedores en ejecución) y docker logs contador (salida de la aplicación). Si necesitas entrar al contenedor para revisar algo, docker exec -it contador sh abre una consola dentro.
Paso 5: el problema de los datos
Ahora haz este experimento. Elimina el contenedor y crea uno nuevo con la misma imagen:
docker rm -f contador
docker run -d --name contador -p 8080:8000 contador:1.0
curl http://localhost:8080El contador volvió a 1. El archivo /datos/visitas.txt vivía en la capa de escritura del contenedor anterior, y esa capa se borró con él. Esto no es un fallo: es el comportamiento esperado, y es justo lo que hace que los contenedores sean reproducibles. Pero significa que cualquier dato que quieras conservar debe vivir fuera del contenedor.
Paso 6: volúmenes para que los datos sobrevivan
Crea un volumen y móntalo en la ruta donde la aplicación escribe:
docker rm -f contador
docker volume create contador-datos
docker run -d --name contador -p 8080:8000 -v contador-datos:/datos contador:1.0La opción -v contador-datos:/datos conecta el volumen con la carpeta /datos del contenedor. Visita la página algunas veces, borra el contenedor y vuelve a crearlo con el mismo -v. Esta vez el contador continúa donde iba. Puedes listar tus volúmenes con docker volume ls y ver sus detalles con docker volume inspect contador-datos.
Volumen con nombre o carpeta del host
Existen dos formas comunes de montar almacenamiento, y cada una tiene su uso:
| Tipo | Ejemplo | Cuándo usarlo |
|---|---|---|
| Volumen con nombre | -v contador-datos:/datos | Datos de la aplicación: bases de datos, archivos subidos, contadores |
| Montaje de carpeta (bind mount) | -v "$(pwd)":/app | Desarrollo: editas el código en tu editor y el contenedor ve los cambios al instante |
En PowerShell, la variable equivalente a $(pwd) es ${PWD}. Ten en cuenta que en desarrollo, para que los cambios de código se reflejen sin reiniciar, el servidor debe tener recarga automática; Gunicorn la ofrece con la opción --reload.
Errores comunes al empezar
"El puerto ya está en uso". Otro proceso ocupa el 8080 de tu computador. Cambia el lado izquierdo del mapeo, por ejemplo -p 8081:8000.
"El nombre del contenedor ya existe". Docker no permite dos contenedores con el mismo nombre, aunque uno esté detenido. Revisa con docker ps -a y elimina el viejo con docker rm.
La página no carga aunque el contenedor está corriendo. Casi siempre es la aplicación escuchando en 127.0.0.1 en lugar de 0.0.0.0, o un mapeo de puertos invertido.
Los cambios en el código no aparecen. Una imagen es una foto del código en el momento de construirla. Si editas app.py, tienes que volver a ejecutar docker build y crear un contenedor nuevo, o usar un montaje de carpeta durante el desarrollo.
El disco se llena. Las imágenes viejas y los contenedores detenidos ocupan espacio. docker system df muestra cuánto, y docker system prune elimina lo que no está en uso. Ojo: añadir --volumes también borra los volúmenes sin usar, y con ellos tus datos.
Chuleta de comandos de este tutorial
| Tarea | Comando |
|---|---|
| Construir una imagen | docker build -t nombre:etiqueta . |
| Listar imágenes | docker images |
| Crear y arrancar un contenedor | docker run -d --name app -p 8080:8000 imagen |
| Ver contenedores (incluidos detenidos) | docker ps -a |
| Ver la salida de la aplicación | docker logs -f app |
| Abrir una consola dentro | docker exec -it app sh |
| Detener y eliminar | docker stop app y docker rm app |
| Crear y listar volúmenes | docker volume create datos y docker volume ls |
Siguiente paso
Con un solo contenedor, escribir los comandos a mano es manejable. Pero en cuanto tu aplicación necesita una base de datos, una caché o un segundo servicio, las opciones de docker run se multiplican y es fácil equivocarse. Ese es el momento de pasar a Docker Compose, que permite describir todos los servicios, puertos y volúmenes en un solo archivo y levantarlos con un único comando. Antes de dar ese salto, practica: cambia el mensaje de la aplicación, reconstruye con la etiqueta 1.1 y ejecuta las dos versiones a la vez en puertos distintos. Cuando eso te salga sin mirar la guía, ya dominas lo esencial.
Si quieres conocer otros artículos parecidos a Docker para principiantes: imágenes, contenedores y tu primer Dockerfile puedes visitar la categoría Guias y Tutoriales.
Deja una respuesta

Contenido que te pude interesar