Docker
¿Qué es?
Docker es una herramienta para empaquetar tu aplicación junto con todo lo que necesita para correr (el intérprete, las librerías, los archivos de configuración) dentro de una unidad aislada llamada contenedor. Un contenedor es como una caja sellada: lleva dentro su propio mini-sistema de archivos, y corre igual en tu portátil, en el de un compañero y en el servidor de producción. La famosa frase "en mi máquina funciona" deja de ser una excusa, porque la máquina viaja dentro de la caja.
¿Cómo funciona?
Hay tres conceptos que no debes confundir:
- Dockerfile: una receta de texto con los pasos para construir tu aplicación.
- Imagen: el resultado de ejecutar esa receta. Es una plantilla inmutable, de solo lectura, lista para distribuir.
- Contenedor: una imagen en ejecución. De una misma imagen puedes lanzar muchos contenedores.
Docker construye la imagen en capas: cada instrucción del Dockerfile genera una capa que se cachea. Si no cambias una capa, Docker la reutiliza y la construcción es casi instantánea. A diferencia de una máquina virtual, los contenedores comparten el núcleo (kernel) del sistema anfitrión, así que arrancan en milisegundos y pesan megabytes, no gigabytes.
¿Para qué sirve?
Sirve para que tu código corra idéntico en todas partes, para aislar dependencias (dos proyectos pueden pedir versiones distintas de la misma librería sin pelearse) y para desplegar de forma reproducible. En este curso es la pieza central del despliegue: dockerizamos la API del módulo anterior para que el servidor de producción solo tenga que descargar una imagen y ejecutarla, sin instalar nada a mano.
Pieza del despliegue que construye este módulo
Construimos la imagen de la API: un Dockerfile real, optimizado con multi-stage y usuario sin privilegios, más un docker-compose.yml para levantarla localmente con un comando.
Paso 1: la API mínima (Python + FastAPI)
# app/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/saludo")
def saludo():
return {"mensaje": "Hola desde el contenedor"}
@app.get("/health")
def health():
return {"status": "ok"}
# requirements.txt
fastapi==0.111.0
uvicorn[standard]==0.30.1
Paso 2: el Dockerfile
# --- Etapa 1: construir dependencias ---
FROM python:3.12-slim AS builder
WORKDIR /app
# Copiamos solo requirements primero para aprovechar la caché de capas:
# si el código cambia pero las dependencias no, esta capa se reutiliza.
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# --- Etapa 2: imagen final, ligera ---
FROM python:3.12-slim
# Creamos un usuario sin privilegios: nunca corras como root en producción.
RUN useradd --create-home appuser
WORKDIR /app
# Traemos solo las dependencias ya instaladas desde la etapa builder.
COPY --from=builder /install /usr/local
COPY app/ ./app/
USER appuser
EXPOSE 8000
# Comprobación de salud que Docker usará para saber si el contenedor vive.
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
El .dockerignore es tan importante como el .gitignore. Crea uno con __pycache__, .git, .venv y *.pyc para no meter basura en la imagen ni invalidar la caché sin necesidad.
Paso 3: construye y ejecuta
docker build -t mi-api:1.0 . # construye la imagen y la etiqueta como mi-api:1.0
docker run -d -p 8000:8000 --name api mi-api:1.0 # la corre en segundo plano (-d)
docker ps # lista contenedores en ejecución
curl http://localhost:8000/saludo # prueba el endpoint
docker logs api # muestra los logs del contenedor
docker stop api && docker rm api # detiene y elimina el contenedor
El -p 8000:8000 mapea el puerto del anfitrión al del contenedor (anfitrión:contenedor). Si olvidas el -p, el contenedor corre pero no puedes acceder a él desde fuera. Es el error número uno de los principiantes.
Paso 4: docker-compose para un comando único
# docker-compose.yml
services:
api:
build: .
image: mi-api:1.0
ports:
- "8000:8000"
restart: unless-stopped
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 30s
timeout: 3s
retries: 3
docker compose up --build -d # construye y levanta todo en segundo plano
docker compose down # apaga y limpia
Tech English: image = imagen (plantilla); container = contenedor (imagen en ejecución); to build = construir; to pull / push = descargar / subir una imagen a un registro; layer = capa.
Ejercicios
-
Imagen reproducible y ligera. Dockeriza la API y reduce el tamaño de la imagen usando multi-stage y
python:3.12-slim. Comparadocker imagesantes y después. Se evalúa quedocker buildfuncione desde cero y que la imagen final pese menos de 200 MB. -
Salud automática. Asegúrate de que
docker psmuestre el contenedor comohealthygracias alHEALTHCHECK. Entrega un scriptverificar.shque construya, levante, espere a que estéhealthyy hagacurlal endpoint. Se evalúa que el script termine con código 0 solo si la API responde.