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)
               └──────────┘
EstadoDescripciónCómo llega ahí
CreatedExiste pero nunca se ejecutódocker create
RunningProceso principal activodocker start o docker run
PausedProcesos congelados en memoriadocker pause
StoppedProceso terminó (exit code disponible)docker stop o el proceso finalizó
RemovedEliminado permanentementedocker 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
ComandoQué 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 execdocker run
ContainerUsa uno existente (debe estar Running)Crea uno nuevo
FilesystemVe cambios hechos en runtimeFilesystem limpio
UsoDebugging, inspecciónEjecutar 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ónQué hacer
Tu app se colgódocker restart
Cambiaste código o Dockerfiledocker rmdocker builddocker run (nuevo)
Actualizaste variables de entornoNuevo 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

FormatoQué 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 CodeSignificado
0Éxito
1Error genérico (exception en Python)
137SIGKILL (docker stop timeout o OOM)
143SIGTERM (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 --rm y 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 logs es tu herramienta #1. Usa -f para tiempo real, --tail N para últimas N líneas, --since para filtrar por tiempo
  • docker exec -it <container> bash te da una terminal dentro de un container en ejecución
  • docker start reinicia un container detenido. docker restart hace stop + start
  • docker inspect muestra toda la metadata en JSON. Usa --format para campos específicos
  • docker cp copia archivos entre container y host
  • El flujo de debugging: logs → inspect → exec → fix → rebuild → run
  • Siempre usa python -u o PYTHONUNBUFFERED=1 para que los logs aparezcan inmediatamente

Recursos Adicionales

  1. Docker Logs Reference — Documentación completa de docker logs
  2. Docker Exec Reference — Documentación de docker exec
  3. Docker Inspect Reference — Referencia con Go templates
  4. Docker CP Reference — Copiar archivos entre container y host
  5. Container Lifecycle (Docker Docs) — Ciclo de vida de containers
  6. Python Docker Best Practices (Real Python) — Docker para proyectos Python
  7. Docker CLI Cheat Sheet — Referencia rápida oficial
  8. Docker Debugging Best Practices — Best practices para logs y debugging