Dockerizar Python
Los contenedores de Python en producción usan imágenes base delgadas (slim), compilaciones multi-etapa, usuarios no root y dependencias fijadas (pinned) a través de uv o pip, produciendo imágenes pequeñas y reproducibles para ECS, Kubernetes o Fly.
Receta
FROM python:3.14-slim AS builder
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev
FROM python:3.14-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY app ./app
ENV PATH="/app/.venv/bin:$PATH"
USER 1000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]Cuándo usar esto:
- Desplegar FastAPI/Django/Flask a cualquier plataforma de contenedores.
- Compilaciones reproducibles a partir de archivos de bloqueo (
lockfiles) en CI. - Aislar dependencias del Python del host.
- Escalado horizontal con imágenes idénticas por réplica.
Ejemplo de Trabajo
Dockerfile multi-etapa con uv, usuario no root y valores predeterminados amigables para la salud (health-friendly).
# syntax=docker/dockerfile:1
FROM python:3.14-slim-bookworm AS builder
WORKDIR /build
RUN pip install --no-cache-dir uv==0.6.0
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-install-project --no-dev
FROM python:3.14-slim-bookworm AS runtime
WORKDIR /app
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PATH="/app/.venv/bin:$PATH"
RUN useradd --create-home --uid 1000 appuser
COPY --from=builder /build/.venv /app/.venv
COPY app ./app
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"
CMD ["gunicorn", "-w", "2", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000", "app.main:app"]docker build -t myapp:local .
docker run --rm -p 8000:8000 -e LOG_LEVEL=info myapp:localQué demuestra esto:
- La etapa
builderinstala las dependencias;runtimesolo copia.venv. PYTHONUNBUFFEREDasegura que los logs se envíen puntualmente.appuserno root reduce el impacto de una posible fuga del contenedor.HEALTHCHECKcomplementa las sondas del orquestador.
Análisis Profundo
Tácticas de Tamaño de Imagen
| Técnica | Ahorro |
|---|---|
Base slim | Capa de SO más pequeña |
| Multi-etapa | Sin herramientas de compilación en la imagen final |
.dockerignore | Omitir .venv, tests, .git |
| Sin caché de pip | --no-cache-dir |
Esenciales de .dockerignore
.git
.venv
__pycache__
.pytest_cache
tests/
Notas de Python
# Fijar el digest para seguridad de la cadena de suministro
FROM python:3.14-slim-bookworm@sha256:... AS runtimeErrores Comunes
- Copiar todo el repositorio antes de instalar dependencias: Causa un
cache busten cada commit. Solución: Copiar primero los archivos de bloqueo (lockfiles), ejecutarRUN uv sync, y luego copiar el código de la aplicación. - Ejecutar como root: Impacto de una posible fuga del contenedor. Solución: Usar
USERno root con permisos de escritura solo en los directorios necesarios. - Dependencias de desarrollo en la imagen de producción: Infla el tamaño y aumenta la superficie de ataque. Solución: Sincronizar con
--no-dev. - Vincular a
127.0.0.1enCMD: Inaccesible desde fuera del contenedor. Solución: Usar0.0.0.0. - Secretos en
ENVo capas: Visibles en el historial de la imagen. Solución: Inyectar en tiempo de ejecución desde un almacén de secretos.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| AWS Lambda container | Picos de baja operación (low-ops) | Conexiones de larga duración |
| PaaS buildpacks | MVP más rápido | Necesidad de control total del Dockerfile |
| Imágenes Nix/conda | Fijación de pila científica (Scientific stack pinning) | API web simple |
Preguntas Frecuentes
¿uv vs pip en Docker?
uv es más rápido y nativo para archivos de bloqueo (lockfiles); se recomienda para proyectos de Python 3.14 con fijación de manifiesto (manifest pins).
¿Necesito gunicorn en el contenedor?
Uvicorn solo funciona para servicios pequeños; Gunicorn gestiona múltiples workers en un solo contenedor.
¿Cómo ejecuto migraciones?
Usar un contenedor de inicialización (init container) o un trabajo (job) separado; no en el mismo CMD que el servidor web a menos que se secuencien cuidadosamente.
¿Dónde van los archivos estáticos?
Recopilarlos en la compilación (django collectstatic) dentro de la imagen o subirlos a S3/CDN en el momento del despliegue.
¿Cómo depuro localmente?
Usar docker compose con montaje de volúmenes solo para desarrollo; la imagen de producción no debe montar el código fuente sobre la imagen.
¿Imágenes ARM vs AMD?
Compilar multi-arquitectura con buildx al desplegar en Graviton o Apple Silicon, coincidiendo con producción.
¿Cómo escaneo imágenes?
CI ejecuta Trivy/Grype al hacer push; parchear la imagen base regularmente.
¿Qué hay de distroless?
Posible copiando venv dentro; depuración más difícil; slim Debian es una opción pragmática por defecto.
¿Cómo paso la configuración?
Variables de entorno y secretos en tiempo de ejecución; no incrustados en las capas de la imagen.
¿Qué tan grandes deben ser las imágenes?
Rastrear el tamaño en CI; investigar cuando la imagen de la API web excede unos pocos cientos de MB sin dependencias de ML.
Relacionado
- Conceptos Básicos de Despliegue - Modelos WSGI/ASGI
- Gunicorn & Uvicorn -
CMDde worker - Configuración y Secretos en Producción - Entorno de ejecución
- Pipelines de CI/CD - Compilar y enviar imágenes
- Mejores Prácticas de Despliegue - Lista de verificación de contenedores
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.