Módulo 9 · Despliegue y operaciones

Lección 40 — Docker

Imágenes, Dockerfile multi-stage y compose para el desarrollo de TicketFlow.

Publicada
En esta lección
  1. Ejercicio 1 — El Dockerfile
  2. Ejercicio 2 — El secreto y la CVE
  3. Ejercicio 3 — El compose
  4. Ejercicio 4 — Las migraciones en el arranque
  5. Ejercicio 5 — La imagen de producción
  6. Entrega

La imagen de TicketFlow, adulta. Sin solutions.md hasta entregar.

Ejercicio 1 — El Dockerfile

  1. Escribe el Dockerfile multi-stage completo (builder + runtime, usuario no-root, healthcheck) y compáralo con tu imagen anterior: docker images — ¿cuántos MB ahorró?
  2. El usuario no-root: corre docker run --rm ticketflow whoami (esperado: el uid 10001) y demuestra que NO puede escribir en /usr (docker run --rm ticketflow touch /usr/x → Permission denied). ¿Por qué esto importa con la 22 en la mano?
  3. La reproducibilidad: build 2 veces y compara los image IDs. Cambia UN comentario del código y re-build: ¿cambia el hash? ¿Qué capa se invalidó y cuáles sobrevivieron (docker build con el output de pasos cacheados)?

Ejercicio 2 — El secreto y la CVE

  1. El test del secreto: docker history ticketflow --no-trunc | grep -iE "secret|password|key" → ¿vacío? Y añade el .dockerignore (.env*, .git, tests/, db.sqlite3). Rompe a propósito: haz un COPY.. SIN dockerignore con un .env presente y busca el secreto con docker export/docker history. Documenta el hallazgo.
  2. El scan: trivy image ticketflow:latest (o docker scout cves). Lista las 3 CVEs top de la capa base. ¿Cuál es "de verdad" (explotable en tu contexto) y cuál es ruido? Escribe el criterio de triage en 3 líneas.
  3. El digest pinneado: cambia python:3.12-slim a python:3.12-slim@sha256:... (el digest de tu versión actual) y verifica que el build sigue idéntico. ¿Qué desastre evita el pinneo?

Ejercicio 3 — El compose

  1. Arma el compose completo (db, redis, web, worker, beat con healthchecks y service_healthy) y levántalo: docker compose up — pega el log del arranque mostrando que web ESPERÓ la salud de db.
  2. El hot-reload: edita una vista desde tu editor y verifica que runserver recarga dentro del contenedor (el bind mount). Después: edita requirements.txt y re-build: ¿el cache de pip sobrevivió a un cambio de código? (la orden de COPY).
  3. El worker en la misma imagen: docker compose run --rm worker shell — un shell dentro del entorno completo (BD, Redis). ¿Qué uso le das en el día a día (el shell de la 24 sin request)?

Ejercicio 4 — Las migraciones en el arranque

  1. Rompe a propósito: command: sh -c "python manage.py migrate && runserver" y escala docker compose up --scale web=3. Observa los 3 migrates competir (logs). ¿Qué lock lo salvó de corromper (la 10) y qué molestia genera?
  2. El patrón adulto: quita migrate del web, corre docker compose run --rm web python manage.py migrate UNA vez (el job del despliegue) y luego escala a 3 réplicas: arranques limpios sin migrate.
  3. La prueba de arranque limpio: mata la web (docker compose restart web) y mide el tiempo hasta el primer 200 en /healthz/. ¿Qué espera y qué no espera el arranque?

Ejercicio 5 — La imagen de producción

  1. El tamaño: lista el top 5 de directorios con docker export | du (o dive si lo tienes): ¿qué es lo más pesado y qué ahorrarías? ¿Vale la pena el effort (¿la distroless de verdad o slim basta)?
  2. El compose de E2E (35): reutiliza el compose con command: pytest tests/e2e y un seed de datos: ¿cuánto tarda el entorno completo de cero (build+up+ready)? La métrica que decide si el E2E corre en cada PR (41).
  3. Escribe el ADR (48) "La imagen de TicketFlow": multi-stage slim, usuario no-root, digest pinneado, config por entorno. Las 3 razones y la alternativa descartada (¿imagen única gigante? ¿VM base?). 10 líneas.

Entrega

Pega el Dockerfile con sus MB, la evidencia del no-root y el secreto, el log del service_healthy y el ADR de imagen. Después: Lección 41 — CI/CD.