Aprendé a optimizar imágenes Docker para aplicaciones Python usando builds multi-stage, dependencias bloqueadas y buenas prácticas de seguridad. Esta guía muestra cómo reducir el tamaño de la imagen, mejorar los tiempos de despliegue y preparar una base más confiable para producción.



Las imágenes Docker demasiado grandes generan varios problemas: tardan más en construirse, ralentizan los despliegues, consumen más almacenamiento, hacen más lento el CI/CD y aumentan la superficie de ataque.

En aplicaciones Python esto suele ocurrir cuando se instala todo dentro de una única imagen: compiladores, dependencias de build, cachés de paquetes, archivos temporales y herramientas que no son necesarias en producción.

Una forma práctica de resolverlo es utilizar builds multi-stage, bloquear dependencias y separar claramente lo que se necesita para construir la aplicación de lo que realmente se necesita para ejecutarla.

En esta guía vas a construir una imagen Docker Python más liviana, reproducible y preparada para servidores cloud como DonWeb Cloud, instancias Linux propias o entornos de laboratorio.


Qué vas a lograr

Al finalizar este tutorial tendrás:

  • una imagen Python más liviana;
  • un Dockerfile basado en multi-stage build;
  • dependencias instaladas de forma más controlada;
  • una imagen final sin herramientas innecesarias de compilación;
  • comandos para construir, ejecutar y validar la imagen;
  • una base más segura para llevar la aplicación a producción.

El objetivo no es aplicar una optimización extrema, sino construir una imagen más limpia, verificable y fácil de mantener.


Cuándo conviene optimizar una imagen Docker Python

Este enfoque resulta útil cuando:

  • la imagen tarda demasiado en construirse;
  • el despliegue en cloud se vuelve lento;
  • el pipeline de CI/CD consume demasiado tiempo;
  • querés reducir superficie de ataque;
  • necesitás builds más reproducibles;
  • querés separar dependencias de desarrollo y producción;
  • la aplicación va a ejecutarse en un servidor cloud o entorno productivo.

También es recomendable cuando el proyecto empieza a crecer y el Dockerfile original ya no resulta fácil de auditar.


Requisitos previos

Antes de comenzar asegurate de contar con:

  • Docker Engine instalado;
  • acceso a una terminal;
  • un proyecto Python funcional;
  • archivo requirements.txt o equivalente;
  • un endpoint de prueba, por ejemplo /health;
  • permisos para construir y ejecutar imágenes Docker;
  • un entorno de laboratorio antes de aplicar cambios en producción.

Si el servidor ya tiene tráfico real, realizá una copia de seguridad y definí una ruta de rollback antes de reemplazar imágenes o reiniciar servicios.


Estructura recomendada del proyecto

Una estructura mínima podría ser la siguiente:

app/├── app.py├── requirements.txt├── Dockerfile└── .dockerignore

En proyectos más grandes puede haber otros directorios, como src/, tests/, migrations/, static/ o config/. Lo importante es que la imagen final copie únicamente lo necesario para ejecutar la aplicación.


Paso 1. Bloquear las dependencias

Antes de optimizar la imagen, revisá cómo se instalan las dependencias.

Un archivo poco controlado como este puede generar builds diferentes con el paso del tiempo:

flaskrequestsgunicorn

Para producción conviene utilizar versiones explícitas:

flask==X.Y.Zrequests==X.Y.Zgunicorn==X.Y.Z

De esta forma, cada build será más predecible y más fácil de auditar.

También podés usar herramientas como pip-tools, Poetry o uv para generar archivos de lock. La decisión depende del flujo de trabajo del equipo, pero el principio es el mismo: evitar instalaciones ambiguas en producción.


Paso 2. Crear un .dockerignore

El archivo .dockerignore evita copiar al contexto de build archivos innecesarios.

Creá un archivo llamado .dockerignore con una base como esta:

.git.gitignore__pycache__/*.pyc*.pyo*.pyd.env.venv/venv/dist/build/.pytest_cache/.mypy_cache/.coveragetests/docs/

Esto ayuda a reducir el tamaño del contexto enviado a Docker y evita incluir archivos sensibles o irrelevantes dentro de la imagen.

Nunca copies archivos .env, credenciales, claves privadas o secretos dentro de la imagen.


Paso 3. Crear un Dockerfile multi-stage

Un build multi-stage permite usar una etapa para instalar o compilar dependencias y otra etapa más liviana para ejecutar la aplicación.

Ejemplo base:

FROM python:3.12-slim AS builderWORKDIR /appENV PYTHONDONTWRITEBYTECODE=1ENV PYTHONUNBUFFERED=1COPY requirements.txt .RUN pip install --upgrade pip \    && pip install --no-cache-dir --prefix=/install -r requirements.txtFROM python:3.12-slim AS runtimeWORKDIR /appENV PYTHONDONTWRITEBYTECODE=1ENV PYTHONUNBUFFERED=1COPY --from=builder /install /usr/localCOPY . .CMD ["python", "app.py"]

La primera etapa instala las dependencias. La segunda etapa copia únicamente lo necesario para ejecutar la aplicación.

Esto evita arrastrar al runtime archivos temporales, cachés o herramientas que solo fueron necesarias durante la construcción.


Paso 4. Construir la imagen

Desde el directorio del proyecto ejecutá:

docker build -t api-python:prod .

Cuando finalice el build, verificá que la imagen exista:

docker image ls api-python:prod

También podés inspeccionar el historial de capas:

docker history api-python:prod

Este comando ayuda a detectar capas demasiado grandes o instrucciones que podrían optimizarse.


Paso 5. Ejecutar la imagen localmente

Probá la imagen antes de moverla a un servidor productivo.

docker run --rm -p 8000:8000 api-python:prod

En otra terminal, validá que la aplicación responda:

curl http://localhost:8000/health

Si tu aplicación no expone /health, reemplazá la ruta por un endpoint real.

La prueba mínima debería confirmar que:

  • el contenedor inicia correctamente;
  • la aplicación queda escuchando en el puerto esperado;
  • las dependencias se instalaron sin errores;
  • el endpoint de validación responde.

Paso 6. Revisar el tamaño de la imagen

Compará el tamaño de la imagen resultante:

docker image ls api-python:prod

Si querés analizar qué capas ocupan más espacio:

docker history api-python:prod

Una imagen más pequeña suele mejorar:

  • tiempos de build;
  • tiempos de pull;
  • uso de almacenamiento;
  • velocidad de despliegue;
  • eficiencia del pipeline CI/CD.

La reducción exacta dependerá de las dependencias, la imagen base y los archivos copiados al contenedor.


Paso 7. Preparar la imagen para producción

Antes de usar la imagen en producción, revisá algunos puntos básicos.

Evitá ejecutar el proceso como root. Podés crear un usuario no privilegiado dentro del Dockerfile:

RUN useradd --create-home --shell /usr/sbin/nologin appuserUSER appuser

Un ejemplo integrado en la etapa runtime podría quedar así:

FROM python:3.12-slim AS runtimeWORKDIR /appENV PYTHONDONTWRITEBYTECODE=1ENV PYTHONUNBUFFERED=1COPY --from=builder /install /usr/localCOPY . .RUN useradd --create-home --shell /usr/sbin/nologin appuser \    && chown -R appuser:appuser /appUSER appuserCMD ["python", "app.py"]

Si la aplicación necesita escribir archivos temporales, definí rutas específicas para eso. No conviene habilitar escritura general si no es necesario.


Configuración base recomendada

Una versión más completa del Dockerfile podría quedar así:

FROM python:3.12-slim AS builderWORKDIR /appENV PYTHONDONTWRITEBYTECODE=1ENV PYTHONUNBUFFERED=1COPY requirements.txt .RUN pip install --upgrade pip \    && pip install --no-cache-dir --prefix=/install -r requirements.txtFROM python:3.12-slim AS runtimeWORKDIR /appENV PYTHONDONTWRITEBYTECODE=1ENV PYTHONUNBUFFERED=1COPY --from=builder /install /usr/localCOPY . .RUN useradd --create-home --shell /usr/sbin/nologin appuser \    && chown -R appuser:appuser /appUSER appuserEXPOSE 8000CMD ["python", "app.py"]

Para aplicaciones web Python, en producción suele ser preferible ejecutar la app con un servidor como Gunicorn en lugar de python app.py.

Por ejemplo:

CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]

Ajustá app:app según el nombre real de tu módulo y de tu aplicación.


Problemas frecuentes

La imagen sigue siendo demasiado grande

Revisá si estás copiando archivos innecesarios. El primer lugar para mirar es .dockerignore.

También verificá si alguna dependencia instala paquetes pesados que podrían separarse, reemplazarse o instalarse solo cuando sean necesarios.


El build falla al instalar dependencias

Algunas librerías Python necesitan paquetes del sistema para compilarse. En ese caso, instalá esas dependencias solo en la etapa builder y evitá copiarlas a la imagen final salvo que sean necesarias en runtime.


El contenedor inicia pero la app no responde

Revisá que la aplicación escuche en 0.0.0.0 y no únicamente en localhost.

También validá el puerto expuesto, el comando de inicio y los logs del contenedor:

docker logs <container_id>

El endpoint /health no existe

Podés crear uno simple en la aplicación o reemplazarlo por una ruta real que confirme que el servicio está activo.

Lo importante es contar con una verificación objetiva antes de publicar cambios.


Faltan variables de entorno

No incluyas secretos dentro de la imagen. Pasá las variables en tiempo de ejecución:

docker run --rm -p 8000:8000 --env-file .env api-python:prod

Recordá que el archivo .env debe estar excluido del build mediante .dockerignore.


Buenas prácticas para producción

Para mantener imágenes Python más seguras y eficientes:

  • usá imágenes base livianas y oficiales;
  • bloqueá versiones de dependencias;
  • evitá copiar archivos innecesarios;
  • no incluyas secretos dentro de la imagen;
  • ejecutá la aplicación con un usuario no root;
  • separá dependencias de build y runtime;
  • eliminá cachés de instalación;
  • analizá periódicamente vulnerabilidades;
  • mantené un endpoint de healthcheck;
  • documentá cómo construir, ejecutar y revertir la imagen.

Estas prácticas no reemplazan otras capas de seguridad, pero ayudan a construir una base más mantenible y auditable.


Consideraciones de seguridad

No publiques una imagen en producción sin probarla antes en un entorno controlado.

Antes de desplegar:

  • verificá que no haya credenciales dentro de la imagen;
  • revisá el historial de capas;
  • escaneá dependencias y paquetes del sistema;
  • confirmá que la aplicación no corre como root;
  • validá que las variables sensibles se inyecten en runtime;
  • conservá la versión anterior para rollback.

Si detectás credenciales expuestas en una imagen ya publicada, rotá esas credenciales antes de continuar con el análisis.

Cloud Serversby Donweb

Todo el poder de la nube a tus proyectos y aplicaciones.
Alojamiento ultra rápido, escalable y con alta disponibilidad.

  • Performance que te sorprenderá
  • Rápida escalabilidad y sin limitaciones
  • Arquitectura de alta disponibilidad
  • Soporte experto y ejecutivos de cuenta
  • Pagos en tu moneda y facturación local

Descubre la mejor solución de Cloud Hosting