La imagen de TicketFlow, adulta. Sin solutions.md hasta entregar.
Ejercicio 1 — El Dockerfile
- 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ó? - 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? - 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
- 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 unCOPY..SIN dockerignore con un.envpresente y busca el secreto condocker export/docker history. Documenta el hallazgo. - El scan:
trivy image ticketflow:latest(odocker 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. - El digest pinneado: cambia
python:3.12-slimapython: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
- 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. - El hot-reload: edita una vista desde tu editor y verifica que runserver recarga dentro del contenedor (el bind mount). Después: edita
requirements.txty re-build: ¿el cache de pip sobrevivió a un cambio de código? (la orden de COPY). - 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
- Rompe a propósito:
command: sh -c "python manage.py migrate && runserver"y escaladocker compose up --scale web=3. Observa los 3 migrates competir (logs). ¿Qué lock lo salvó de corromper (la 10) y qué molestia genera? - El patrón adulto: quita migrate del web, corre
docker compose run --rm web python manage.py migrateUNA vez (el job del despliegue) y luego escala a 3 réplicas: arranques limpios sin migrate. - 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
- 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)? - El compose de E2E (35): reutiliza el compose con
command: pytest tests/e2ey 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). - 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.