Módulo 2: Images y Containers
6. Ciclo de Vida de Containers
Descripción
Hasta ahora sabes construir imágenes y correr containers. Pero ¿qué haces cuando algo no funciona? Un container se cae, tu app no responde, los logs muestran un error críptico. Esta cápsula te enseña las herramientas de debugging: docker logs, docker exec, docker inspect, docker cp, y el flujo completo para diagnosticar problemas.
Por qué importa: La diferencia entre un desarrollador que "sabe Docker" y uno que lo domina es qué hace cuando algo falla. Los comandos de esta cápsula son tu kit de emergencia.
Conexión con el módulo: En las cápsulas 02-05 aprendiste a construir imágenes. Ahora completas el ciclo aprendiendo a gestionar y debuggear containers. En la cápsula 07 verás CMD vs ENTRYPOINT, y en la 08 integrarás todo en el proyecto.
Estados de un Container
Un container pasa por estados bien definidos:
docker create
│
▼
┌─────────┐
│ CREATED │
└────┬─────┘
│ docker start
▼
┌─────────┐ docker pause ┌─────────┐
│ RUNNING │ ──────────────────→ │ PAUSED │
│ │ ←────────────────── │ │
└────┬─────┘ docker unpause └─────────┘
│
│ docker stop / proceso termina
▼
┌─────────┐
│ STOPPED │ (Exited)
└────┬─────┘
│ docker rm
▼
┌─────────┐
│ REMOVED │ (ya no existe)
└──────────┘
| Estado | Descripción | Cómo llega ahí |
|---|---|---|
| Created | Existe pero nunca se ejecutó | docker create |
| Running | Proceso principal activo | docker start o docker run |
| Paused | Procesos congelados en memoria | docker pause |
| Stopped | Proceso terminó (exit code disponible) | docker stop o el proceso finalizó |
| Removed | Eliminado permanentemente | docker rm |
Transiciones en la práctica
docker run -d --name mi-app python:3.11-slim python -c "import time; time.sleep(300)"
docker pause mi-app # Running → Paused
docker unpause mi-app # Paused → Running
docker stop mi-app # Running → Stopped (SIGTERM → SIGKILL si no responde en 10s)
docker start mi-app # Stopped → Running (mismo container, mismo ID)
docker rm mi-app # Stopped → Removed
docker run combina create + start. La mayoría del tiempo usarás docker run, pero saber que son dos operaciones separadas te ayuda a entender el ciclo.
docker logs: Tu Herramienta #1 de Debugging
docker logs muestra todo lo que el proceso principal imprimió a stdout y stderr. Es lo primero que revisas cuando algo falla.
App de ejemplo
Crea app_logs.py:
import time, sys, json
from datetime import datetime
print("=" * 50)
print("AI Data Processor - Starting up...")
print(f"Start time: {datetime.now().isoformat()}")
sys.stdout.flush()
sources = ["users.csv", "embeddings.npy", "config.json", "model_weights.bin"]
for i, source in enumerate(sources, 1):
time.sleep(2)
if source == "model_weights.bin":
print(f"[ERROR] Failed to load {source}: FileNotFoundError", file=sys.stderr)
else:
print(json.dumps({"step": i, "source": source, "status": "loaded"}))
sys.stdout.flush()
print("[WARN] Processing completed with errors")
FROM python:3.11-slim
WORKDIR /app
COPY app_logs.py .
CMD ["python", "-u", "app_logs.py"]
La flag -u desactiva el buffering de stdout en Python — sin ella, los logs pueden llegar con retraso.
docker build -t log-demo .
docker run -d --name log-test log-demo
Comandos esenciales
# Todos los logs
docker logs log-test
==================================================
AI Data Processor - Starting up...
Start time: 2026-03-08T14:23:01.234567
{"step": 1, "source": "users.csv", "status": "loaded"}
{"step": 2, "source": "embeddings.npy", "status": "loaded"}
{"step": 3, "source": "config.json", "status": "loaded"}
[ERROR] Failed to load model_weights.bin: FileNotFoundError
[WARN] Processing completed with errors
# Seguir en tiempo real (como tail -f)
docker run -d --name streaming python:3.11-slim python -u -c "
import time
from datetime import datetime
i = 0
while True:
i += 1
print(f'[{datetime.now().isoformat()}] Processing batch {i}...')
time.sleep(3)
"
docker logs -f streaming # Ctrl+C para salir (container sigue corriendo)
docker logs --tail 5 streaming # Últimas 5 líneas
docker logs --since 5m streaming # Últimos 5 minutos
docker logs --tail 10 -f streaming # Últimas 10 + seguir en tiempo real
| Comando | Qué hace |
|---|---|
docker logs <container> | Todos los logs |
docker logs -f <container> | Seguir en tiempo real |
docker logs --tail N <container> | Últimas N líneas |
docker logs --since 5m <container> | Últimos 5 minutos |
docker stop streaming log-test && docker rm streaming log-test
docker exec: Entrar a un Container en Ejecución
docker exec ejecuta un comando dentro de un container que ya está corriendo.
docker run -d --name exec-demo python:3.11-slim python -u -c "
import time
while True:
time.sleep(10)
"
Terminal interactiva
docker exec -it exec-demo bash
root@a1b2c3d4e5f6:/app#
Estás dentro del container. Todo ocurre en su filesystem, no en tu máquina.
pwd # /app
python --version # Python 3.11.12
pip list # Paquetes instalados
cat /etc/os-release # Debian GNU/Linux 12
exit
Variables de entorno
docker exec exec-demo env
PATH=/usr/local/bin:/usr/local/sbin:...
HOSTNAME=a1b2c3d4e5f6
PYTHON_VERSION=3.11.12
HOME=/root
Comandos puntuales (sin abrir bash)
docker exec exec-demo python -c "import sys; print(f'Path: {sys.executable}')"
Path: /usr/local/bin/python
docker exec | docker run | |
|---|---|---|
| Container | Usa uno existente (debe estar Running) | Crea uno nuevo |
| Filesystem | Ve cambios hechos en runtime | Filesystem limpio |
| Uso | Debugging, inspección | Ejecutar aplicaciones |
docker stop exec-demo && docker rm exec-demo
docker start y docker restart
Cuando detienes un container con docker stop, queda en estado Exited. Puedes reiniciarlo sin crear uno nuevo:
docker run -d --name restart-demo python:3.11-slim python -u -c "
import time
from datetime import datetime
print(f'Started at: {datetime.now().isoformat()}')
while True:
time.sleep(5)
"
docker stop restart-demo # Running → Stopped
docker start restart-demo # Stopped → Running (mismo container)
docker restart restart-demo # Equivale a stop + start
| Situación | Qué hacer |
|---|---|
| Tu app se colgó | docker restart |
| Cambiaste código o Dockerfile | docker rm → docker build → docker run (nuevo) |
| Actualizaste variables de entorno | Nuevo container (env vars se definen al crear) |
docker stop restart-demo && docker rm restart-demo
docker inspect: Toda la Metadata
docker inspect muestra todo sobre un container en JSON. Usa --format para extraer campos específicos:
docker run -d --name inspect-demo -e API_KEY=sk-test-123 -e MODEL=gpt-4 \
python:3.11-slim python -u -c "import time; [time.sleep(10) for _ in iter(int, 1)]"
docker inspect --format '{{.State.Status}}' inspect-demo
running
docker inspect --format '{{.NetworkSettings.IPAddress}}' inspect-demo
172.17.0.2
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' inspect-demo
API_KEY=sk-test-123
MODEL=gpt-4
PATH=/usr/local/bin:...
PYTHON_VERSION=3.11.12
Campos útiles
| Formato | Qué muestra |
|---|---|
{{.State.Status}} | running, exited, paused |
{{.State.ExitCode}} | 0 = éxito, 1 = error, 137 = SIGKILL |
{{.NetworkSettings.IPAddress}} | IP dentro de la red Docker |
{{.Config.Env}} | Variables de entorno |
{{.State.OOMKilled}} | true si fue Out of Memory |
Exit codes comunes
| Exit Code | Significado |
|---|---|
| 0 | Éxito |
| 1 | Error genérico (exception en Python) |
| 137 | SIGKILL (docker stop timeout o OOM) |
| 143 | SIGTERM (docker stop normal) |
docker stop inspect-demo && docker rm inspect-demo
docker cp: Copiar Archivos Entre Container y Host
docker cp copia archivos en ambas direcciones. Funciona con containers detenidos o corriendo.
docker run -d --name cp-demo python:3.11-slim python -u -c "
import json, time
from datetime import datetime
results = {'model': 'text-embedding-ada-002', 'accuracy': 0.94,
'timestamp': datetime.now().isoformat()}
with open('/app/results.json', 'w') as f:
json.dump(results, f, indent=2)
print('Results saved')
time.sleep(300)
"
# Container → Host
docker cp cp-demo:/app/results.json ./results.json
cat results.json
{
"model": "text-embedding-ada-002",
"accuracy": 0.94,
"timestamp": "2026-03-08T14:50:01.234567"
}
# Host → Container
echo '{"model": "gpt-4", "debug": true}' > config.json
docker cp config.json cp-demo:/app/config.json
docker exec cp-demo cat /app/config.json
{"model": "gpt-4", "debug": true}
docker cp es para debugging y extracción de datos, no para el flujo normal (para eso usas COPY en el Dockerfile).
docker stop cp-demo && docker rm cp-demo
rm -f results.json config.json
El Flujo Completo de Debugging
Cuando tu container no funciona, sigue este flujo sistemático.
La app que falla
Crea ai_processor.py:
import json, os
def load_config():
config_path = os.environ.get("CONFIG_PATH", "/app/config.json")
with open(config_path) as f:
return json.load(f)
def main():
print("AI Processor v1.0 starting...")
config = load_config()
print(f"Processing with model: {config['model']}")
if __name__ == "__main__":
main()
FROM python:3.11-slim
WORKDIR /app
COPY ai_processor.py .
CMD ["python", "ai_processor.py"]
docker build -t ai-processor .
docker run --name proc-test ai-processor
AI Processor v1.0 starting...
Traceback (most recent call last):
...
FileNotFoundError: [Errno 2] No such file or directory: '/app/config.json'
Paso 1: Logs → identificar el error
docker logs proc-test # FileNotFoundError: /app/config.json
Paso 2: Inspect → verificar estado y env vars
docker inspect --format '{{.State.ExitCode}}' proc-test # 1
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' proc-test | grep CONFIG
# (vacío — no hay CONFIG_PATH definida)
Paso 3: Explorar filesystem
docker run -it --rm ai-processor bash
ls -la /app/ # Solo ai_processor.py — no hay config.json
exit
Paso 4: Fix → Rebuild → Verify
echo '{"model": "gpt-4", "batch_size": 32}' > config.json
# Agregar al Dockerfile: COPY config.json .
docker rm proc-test
docker build -t ai-processor .
docker run --name proc-test ai-processor
AI Processor v1.0 starting...
Processing with model: gpt-4
El flujo resumido
Container falla
→ 1. docker logs → ¿Qué error hay?
→ 2. docker inspect → ¿Exit code? ¿Env vars correctas?
→ 3. docker exec/run bash → Explorar filesystem
→ 4. Fix → docker rm → docker build → docker run
docker rm proc-test
rm -f ai_processor.py config.json
Troubleshooting
Problema 1: "No such container"
Error response from daemon: No such container: mi-app
Verifica con docker ps -a. Posibles causas:
- ✅ Typo en el nombre
- ✅ Fue eliminado con
docker rm - ✅ Usaste
--rmy ya terminó
Problema 2: docker logs no muestra nada
Python buferea stdout por defecto. Soluciones:
CMD ["python", "-u", "app.py"]
# o
ENV PYTHONUNBUFFERED=1
CMD ["python", "app.py"]
Problema 3: docker exec falla con "is not running"
Solo puedes hacer exec en containers Running. Alternativas:
docker start mi-app && docker exec -it mi-app bash
docker run -it --rm <imagen> bash # Crear uno nuevo
Problema 4: Exit code 137 (container muere inesperadamente)
Exit code 137 = SIGKILL. Causas:
- ⚠️ Out of Memory: Verifica con
docker inspect --format '{{.State.OOMKilled}}' mi-app - ⚠️ docker stop timeout: Docker esperó 10s para SIGTERM y envió SIGKILL
Ejercicios
Ejercicio 1: Investigar con logs
Corre este container y usa docker logs para responder: ¿cuántos batches exitosos? ¿Qué error ocurrió?
docker run -d --name logs-ex python:3.11-slim python -u -c "
import time, random
random.seed(42)
for i in range(1, 11):
time.sleep(1)
if random.random() < 0.2:
print(f'[ERROR] Batch {i}: Connection timeout')
else:
print(f'[OK] Batch {i}: Processed 100 embeddings')
print('Job finished')
"
Ver solución
docker logs logs-ex
[OK] Batch 1: Processed 100 embeddings
[OK] Batch 2: Processed 100 embeddings
...
[ERROR] Batch 7: Connection timeout
...
Job finished
9 batches exitosos, 1 error en batch 7 (Connection timeout).
docker rm logs-ex
Ejercicio 2: Explorar con exec
Crea un container y usa docker exec para: verificar versión de Python, listar paquetes, ejecutar un script.
docker run -d --name exec-ex python:3.11-slim sleep 300
Ver solución
docker exec exec-ex python --version # Python 3.11.12
docker exec exec-ex pip list # pip, setuptools, wheel
docker exec exec-ex python -c "
import platform
print(f'OS: {platform.system()} {platform.release()}')
print(f'Arch: {platform.machine()}')
"
OS: Linux 6.10.14-linuxkit
Arch: aarch64
docker stop exec-ex && docker rm exec-ex
Ejercicio 3: Diagnosticar con inspect
Extrae estado, exit code, variables custom e IP de este container:
docker run -d --name inspect-ex -e OPENAI_MODEL=gpt-4 -e TEMPERATURE=0.7 python:3.11-slim sleep 120
Ver solución
docker inspect --format '{{.State.Status}}' inspect-ex # running
docker inspect --format '{{.State.ExitCode}}' inspect-ex # 0
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' inspect-ex | grep -E "OPENAI|TEMP"
# OPENAI_MODEL=gpt-4
# TEMPERATURE=0.7
docker inspect --format '{{.NetworkSettings.IPAddress}}' inspect-ex # 172.17.0.2
docker stop inspect-ex && docker rm inspect-ex
Ejercicio 4: Extraer archivos con docker cp
Extrae el reporte que genera este container:
docker run -d --name cp-ex python:3.11-slim python -u -c "
import json, time
from datetime import datetime
report = {'experiment': 'embedding-test', 'accuracy': 0.967,
'generated_at': datetime.now().isoformat()}
with open('/tmp/report.json', 'w') as f:
json.dump(report, f, indent=2)
print('Report generated')
time.sleep(120)
"
Ver solución
sleep 3
docker cp cp-ex:/tmp/report.json ./report.json
cat report.json
{
"experiment": "embedding-test",
"accuracy": 0.967,
"generated_at": "2026-03-08T15:30:01.234567"
}
docker stop cp-ex && docker rm cp-ex
rm -f report.json
Ejercicio 5: Flujo completo de debugging
Este container falla. Diagnostica y hazlo funcionar:
docker run --name debug-ex python:3.11-slim python -u -c "
import os, json
api_key = os.environ['API_KEY']
config = json.loads(os.environ.get('APP_CONFIG', '{}'))
print(f'Using {config[\"model\"]} with key {api_key[:8]}...')
"
Ver solución
docker logs debug-ex # KeyError: 'API_KEY'
docker inspect --format '{{.State.ExitCode}}' debug-ex # 1
Falta la variable API_KEY. Fix:
docker rm debug-ex
docker run --name debug-ex \
-e API_KEY=sk-test-key-12345 \
-e APP_CONFIG='{"model":"gpt-4"}' \
python:3.11-slim python -u -c "
import os, json
api_key = os.environ['API_KEY']
config = json.loads(os.environ.get('APP_CONFIG', '{}'))
print(f'Using {config[\"model\"]} with key {api_key[:8]}...')
"
Using gpt-4 with key sk-test-...
docker rm debug-ex
Ejercicio 6: Monitorear un proceso
Monitorea este pipeline con docker logs -f y extrae los resultados finales con docker cp:
docker run -d --name monitor-ex python:3.11-slim python -u -c "
import time, json, random
from datetime import datetime
random.seed(123)
stats = {'processed': 0, 'errors': 0, 'total_tokens': 0}
for i in range(1, 16):
time.sleep(1)
tokens = random.randint(100, 500)
stats['processed'] += 1
stats['total_tokens'] += tokens
if random.random() < 0.1:
stats['errors'] += 1
print(f'ERROR batch {i}: Rate limit')
else:
print(f'OK batch {i}: {tokens} tokens')
with open('/tmp/stats.json', 'w') as f:
json.dump(stats, f, indent=2)
print(f'Complete: {json.dumps(stats)}')
"
Ver solución
docker logs --tail 5 -f monitor-ex # Monitorear en tiempo real, Ctrl+C para salir
Cuando termine:
docker cp monitor-ex:/tmp/stats.json ./stats.json
cat stats.json
{
"processed": 15,
"errors": 2,
"total_tokens": 4521
}
docker rm monitor-ex
rm -f stats.json
Resumen
- Un container pasa por cinco estados: Created → Running → Paused → Stopped → Removed
docker logses tu herramienta #1. Usa-fpara tiempo real,--tail Npara últimas N líneas,--sincepara filtrar por tiempodocker exec -it <container> bashte da una terminal dentro de un container en ejecucióndocker startreinicia un container detenido.docker restarthace stop + startdocker inspectmuestra toda la metadata en JSON. Usa--formatpara campos específicosdocker cpcopia archivos entre container y host- El flujo de debugging: logs → inspect → exec → fix → rebuild → run
- Siempre usa
python -uoPYTHONUNBUFFERED=1para que los logs aparezcan inmediatamente
Recursos Adicionales
- Docker Logs Reference — Documentación completa de
docker logs - Docker Exec Reference — Documentación de
docker exec - Docker Inspect Reference — Referencia con Go templates
- Docker CP Reference — Copiar archivos entre container y host
- Container Lifecycle (Docker Docs) — Ciclo de vida de containers
- Python Docker Best Practices (Real Python) — Docker para proyectos Python
- Docker CLI Cheat Sheet — Referencia rápida oficial
- Docker Debugging Best Practices — Best practices para logs y debugging