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
Dockerfilebasado 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.txto 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└── .dockerignoreEn 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:
flaskrequestsgunicornPara producción conviene utilizar versiones explícitas:
flask==X.Y.Zrequests==X.Y.Zgunicorn==X.Y.ZDe 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:prodTambién podés inspeccionar el historial de capas:
docker history api-python:prodEste 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:prodEn otra terminal, validá que la aplicación responda:
curl http://localhost:8000/healthSi 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:prodSi querés analizar qué capas ocupan más espacio:
docker history api-python:prodUna 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 appuserUn 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:prodRecordá 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.





