En nuestra entrada anterior definimos qué es Docker: una herramienta para empaquetar aplicaciones y asegurar que funcionen igual en todas partes. Pero, ¿cómo creamos ese paquete? ¿Cómo le decimos a Docker qué debe incluir?
La respuesta es el Dockerfile.
¿Qué es un Dockerfile?
Es un archivo de texto simple (sin extensión) que contiene una lista ordenada de instrucciones. Docker lee este archivo y ejecuta cada instrucción paso a paso para construir la Imagen de tu contenedor.
La estructura básica
Un Dockerfile típico sigue este flujo lógico:
- FROM: ¿De qué base partimos? (Ej. Un sistema con Python instalado).
- WORKDIR: ¿En qué carpeta vamos a trabajar dentro del contenedor?
- COPY: Copiamos nuestros archivos desde nuestra máquina al contenedor.
- RUN: Ejecutamos comandos de instalación (librerías, dependencias).
- CMD: ¿Qué comando debe ejecutarse cuando arranque el contenedor?
El problema: No todos los Dockerfiles son iguales
Es fácil escribir un Dockerfile que funcione. Lo difícil es escribir uno que sea rápido, ligero y seguro.
Veamos un ejemplo de un Dockerfile no optimizado (lo que solemos hacer al empezar):
Dockerfile
# ❌ EJEMPLO NO OPTIMIZADO
FROM python:latest
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD ["python", "main.py"]
¿Por qué es malo?
- Imagen Pesada: Usa
python:latest, que es una imagen base muy grande (casi 1GB). - Sin Caché de Capas: Copia todo el código antes de instalar dependencias. Si cambias una línea de código en tu
main.py, Docker volverá a instalar TODAS las librerías de nuevo. - Seguridad: Al usar
latest, no tienes control sobre qué versión exacta estás usando, lo que puede romper tu app en el futuro.
Estrategias de Optimización
Para mejorar esto, aplicaremos tres conceptos clave que se enseñan en el desarrollo profesional con Docker:
1. Elegir la Base Image Correcta
En lugar de usar la versión completa (latest), usa versiones específicas y reducidas como Alpine o Slim.
- Recomendación:
python:3.9-slimonode:16-alpine. Son imágenes recortadas que solo tienen lo justo y necesario. Menos peso = despliegues más rápidos y menos vulnerabilidades.
2. Aprovechar el Layer Caching
Docker construye el contenedor en «capas». Si una capa no ha cambiado, Docker usa la versión en memoria caché.
- El Truco: Copia primero los archivos de dependencias (
package.jsonorequirements.txt) e instálalos. Solo después copia el resto del código fuente. - Resultado: Si cambias tu código pero no tus dependencias, Docker se salta la instalación y la construcción tarda segundos en lugar de minutos.
3. Multi-Stage Builds (Construcción en etapas)
Para lenguajes compilados (como Go) o frameworks de frontend (como React), no necesitas las herramientas de desarrollo en producción.
- Cómo funciona: Usas una imagen para compilar/construir (Stage 1) y luego copias solo el archivo resultante a una imagen final limpia y diminuta (Stage 2).
4. Usar .dockerignore
Igual que el .gitignore. Evita copiar archivos basura locales, carpetas .git, o archivos temporales dentro del contenedor. Esto reduce el tamaño y evita errores.
El Resultado: Un Dockerfile Optimizado
Aplicando lo anterior, así se ve nuestro Dockerfile profesional para una app de Python:
Dockerfile
# ✅ EJEMPLO OPTIMIZADO
# 1. Fijamos versión específica y ligera (Slim)
FROM python:3.9-slim
# 2. Establecemos directorio de trabajo
WORKDIR /app
# 3. OPTIMIZACIÓN DE CACHÉ:
# Copiamos SOLO los requerimientos primero
COPY requirements.txt .
# Instalamos dependencias (esta capa se guardará en caché si requirements.txt no cambia)
RUN pip install --no-cache-dir -r requirements.txt
# 4. Ahora sí, copiamos el resto del código
COPY . .
# 5. Comando de inicio
CMD ["python", "main.py"]
Conclusión
Optimizar un Dockerfile no es solo cuestión de «ahorrar espacio». Se trata de:
- Velocidad: Optimizarás el flujo de trabajo del equipo al evitar que esperar 5 minutos cada vez que construyen la imagen.
- Seguridad: Menos archivos y versiones controladas resultan en una mayor seguridad.
- Eficiencia: Despliegues más rápidos y baratos en la nube.
