Creación de Dockerfiles: Guía Completa con Mejores Prácticas
Los Dockerfiles son el plano para crear imágenes Docker, definiendo todo desde el sistema operativo base hasta las dependencias de la aplicación y configuraciones de tiempo de ejecución. Esta guía completa te enseña cómo escribir Dockerfiles eficientes, seguros y listos para producción para cualquier aplicación.
Tabla de Contenidos
- Introducción a los Dockerfiles
- Requisitos Previos
- Conceptos Básicos de Dockerfile
- Instrucciones Esenciales de Dockerfile
- Construcción de Imágenes Docker
- Construcciones Multi-Etapa
- Ejemplos del Mundo Real
- Técnicas de Optimización
- Mejores Prácticas de Seguridad
- Solución de Problemas
- Conclusión
Introducción a los Dockerfiles
Un Dockerfile es un documento de texto que contiene instrucciones para construir una imagen Docker. Cada instrucción crea una capa en la imagen final, y Docker almacena en caché estas capas para acelerar construcciones subsecuentes. Comprender cómo escribir Dockerfiles eficientes es crucial para crear imágenes de contenedor optimizadas, seguras y mantenibles.
Por Qué Importan los Dockerfiles
- Reproducibilidad: El mismo Dockerfile produce imágenes idénticas
- Control de Versiones: Rastrea cambios de imágenes como código fuente
- Automatización: Integra con pipelines CI/CD
- Documentación: Sirve como documentación de infraestructura
- Portabilidad: Construye una vez, ejecuta en cualquier lugar
Requisitos Previos
Antes de crear Dockerfiles, asegúrate de tener:
- Docker Engine instalado y ejecutándose
- Comprensión básica de conceptos de Docker
- Conocimiento de las dependencias de tu aplicación
- Editor de texto para escribir Dockerfiles
- Acceso a terminal para construir imágenes
Verifica la instalación de Docker:
docker --version
docker info
Conceptos Básicos de Dockerfile
Estructura Básica
Un Dockerfile consiste en instrucciones (en mayúsculas) seguidas de argumentos:
# Comment
INSTRUCTION arguments
Creando Tu Primer Dockerfile
Crea un archivo llamado Dockerfile (sin extensión):
mkdir my-docker-app
cd my-docker-app
nano Dockerfile
Ejemplo simple:
# Use official base image
FROM ubuntu:22.04
# Set working directory
WORKDIR /app
# Copy application files
COPY app.py .
# Install dependencies
RUN apt-get update && apt-get install -y python3
# Define command to run
CMD ["python3", "app.py"]
Nomenclatura de Archivos
- Nombre estándar:
Dockerfile(recomendado) - Nombres personalizados:
Dockerfile.dev,Dockerfile.prod - Construir con nombre personalizado:
docker build -f Dockerfile.dev .
Instrucciones Esenciales de Dockerfile
FROM - Imagen Base
Especifica la imagen padre para tu construcción:
# Official image
FROM ubuntu:22.04
# Specific version (recommended)
FROM node:18.17.0-alpine
# Multiple stages
FROM node:18 AS builder
FROM nginx:alpine AS production
Mejores Prácticas:
- Siempre especifica etiquetas de versión (evita
latest) - Usa imágenes oficiales cuando sea posible
- Prefiere imágenes basadas en Alpine para menor tamaño
LABEL - Metadatos
Agrega metadatos a las imágenes:
LABEL maintainer="[email protected]"
LABEL version="1.0"
LABEL description="Production web application"
LABEL org.opencontainers.image.source="https://github.com/user/repo"
WORKDIR - Directorio de Trabajo
Establece el directorio de trabajo para instrucciones subsecuentes:
# Set working directory
WORKDIR /app
# Creates directory if it doesn't exist
WORKDIR /var/www/html
# Relative paths work too
WORKDIR /app
WORKDIR src # Now in /app/src
Mejor Práctica: Usa rutas absolutas y establece WORKDIR antes de COPY/ADD.
COPY vs ADD
Copia archivos del contexto de construcción a la imagen:
# COPY - Simple file copying (preferred)
COPY package.json .
COPY src/ /app/src/
COPY --chown=appuser:appuser app.py /app/
# ADD - Advanced features (use sparingly)
ADD https://example.com/file.tar.gz /tmp/ # Downloads URL
ADD archive.tar.gz /app/ # Auto-extracts archives
Mejor Práctica: Usa COPY a menos que necesites las características especiales de ADD.
RUN - Ejecutar Comandos
Ejecuta comandos durante la construcción de la imagen:
# Shell form (runs in /bin/sh -c)
RUN apt-get update && apt-get install -y curl
# Exec form (recommended)
RUN ["/bin/bash", "-c", "echo hello"]
# Multiple commands (chain with &&)
RUN apt-get update && \
apt-get install -y \
python3 \
python3-pip \
curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Install Python packages
RUN pip install --no-cache-dir -r requirements.txt
Mejores Prácticas:
- Encadena comandos con
&¶ reducir capas - Limpia en el mismo comando RUN
- Usa
--no-cache-dirpara instalaciones de pip - Elimina cachés del gestor de paquetes
ENV - Variables de Entorno
Establece variables de entorno:
# Set single variable
ENV NODE_ENV=production
# Set multiple variables
ENV APP_HOME=/app \
APP_USER=appuser \
APP_PORT=3000
# Use in subsequent commands
ENV PATH="/app/bin:${PATH}"
ARG - Argumentos de Construcción
Define variables de tiempo de construcción:
# Define argument with default
ARG NODE_VERSION=18
ARG BUILD_DATE
# Use in FROM
FROM node:${NODE_VERSION}-alpine
# Use in RUN
RUN echo "Built on ${BUILD_DATE}"
# ARG vs ENV
ARG BUILD_ENV=dev
ENV RUNTIME_ENV=${BUILD_ENV} # Convert ARG to ENV
Construye con argumentos:
docker build --build-arg NODE_VERSION=20 --build-arg BUILD_DATE=$(date -u +"%Y-%m-%d") .
EXPOSE - Documentar Puertos
Documenta qué puertos escucha el contenedor:
# Single port
EXPOSE 8080
# Multiple ports
EXPOSE 80 443
# With protocol
EXPOSE 8080/tcp
EXPOSE 53/udp
Nota: EXPOSE es solo documentación. Usa el flag -p al ejecutar el contenedor.
USER - Establecer Usuario
Especifica qué usuario ejecuta el contenedor:
# Create user and switch
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
# Switch to user by UID
USER 1000
# Switch back to root if needed
USER root
RUN apt-get install -y something
USER appuser
Mejor Práctica: Siempre ejecuta contenedores como usuario no root.
VOLUME - Puntos de Montaje
Crea puntos de montaje:
# Define volumes
VOLUME /data
VOLUME ["/var/log", "/var/db"]
Nota: No puedes especificar ruta del host en Dockerfile. Usa el flag -v al ejecutar.
CMD vs ENTRYPOINT
Define el comando predeterminado del contenedor:
# CMD - Can be overridden
CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]
CMD node server.js # Shell form
# ENTRYPOINT - Main executable
ENTRYPOINT ["python", "app.py"]
# ENTRYPOINT + CMD (arguments)
ENTRYPOINT ["python"]
CMD ["app.py"]
# Override: docker run image script.py
# Use both for flexibility
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["start"]
Mejores Prácticas:
- Usa ENTRYPOINT para el ejecutable principal
- Usa CMD para argumentos predeterminados
- Prefiere la forma exec sobre la forma shell
HEALTHCHECK
Define verificación de salud del contenedor:
# HTTP health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# Simple health check
HEALTHCHECK CMD pg_isready -U postgres || exit 1
# Disable inherited health check
HEALTHCHECK NONE
ONBUILD
Agrega instrucciones de activación:
# In base image
ONBUILD COPY package.json /app/
ONBUILD RUN npm install
# Triggers when image is used as base
FROM my-base-image # Executes ONBUILD instructions
Construcción de Imágenes Docker
Comando de Construcción Básico
# Build from current directory
docker build -t my-app:latest .
# Build from different directory
docker build -t my-app:latest /path/to/context
# Build with custom Dockerfile
docker build -t my-app:latest -f Dockerfile.prod .
Contexto de Construcción
El contexto de construcción es el conjunto de archivos en la RUTA o URL especificada:
# Current directory
docker build .
# Specific directory
docker build /path/to/context
# Git repository
docker build https://github.com/user/repo.git#branch
Etiquetado de Imágenes
# Single tag
docker build -t my-app:1.0 .
# Multiple tags
docker build -t my-app:1.0 -t my-app:latest .
# With registry
docker build -t registry.example.com/my-app:1.0 .
Argumentos de Construcción
# Pass build arguments
docker build --build-arg ENV=production --build-arg VERSION=1.0 .
# Multiple arguments from file
docker build --build-arg-file build-args.txt .
Archivo .dockerignore
Excluye archivos del contexto de construcción:
# .dockerignore
.git
.gitignore
.env
node_modules
npm-debug.log
Dockerfile
.dockerignore
README.md
*.md
.vscode
.idea
Opciones de Construcción
# No cache
docker build --no-cache -t my-app:latest .
# Pull latest base image
docker build --pull -t my-app:latest .
# Specify target stage
docker build --target production -t my-app:latest .
# Set memory limit
docker build --memory 2g -t my-app:latest .
# Squash layers (experimental)
docker build --squash -t my-app:latest .
Construcciones Multi-Etapa
Las construcciones multi-etapa crean imágenes de producción optimizadas al separar entornos de construcción y tiempo de ejecución:
Construcción Multi-Etapa Básica
# Build stage
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER node
CMD ["node", "dist/server.js"]
Múltiples Etapas
# Dependencies stage
FROM node:18-alpine AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Test stage
FROM builder AS tester
RUN npm run test
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
Construye etapa específica:
# Build and test
docker build --target tester -t my-app:test .
# Build production
docker build --target production -t my-app:latest .
Copiar Desde Imágenes Externas
# Copy from specific image
FROM alpine:latest
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
Ejemplos del Mundo Real
Aplicación Node.js
# Multi-stage Node.js app
FROM node:18-alpine AS builder
WORKDIR /app
# Copy dependency files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production && \
npm cache clean --force
# Copy source code
COPY . .
# Build application
RUN npm run build
# Production stage
FROM node:18-alpine
# Add non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# Copy built files and dependencies
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./
# Set environment
ENV NODE_ENV=production
# Switch to non-root user
USER nodejs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD node healthcheck.js
# Start application
CMD ["node", "dist/server.js"]
Aplicación Python Flask
FROM python:3.11-slim AS builder
WORKDIR /app
# Install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc && \
rm -rf /var/lib/apt/lists/*
# Copy requirements
COPY requirements.txt .
# Install Python dependencies
RUN pip install --user --no-cache-dir -r requirements.txt
# Production stage
FROM python:3.11-slim
WORKDIR /app
# Copy dependencies from builder
COPY --from=builder /root/.local /root/.local
# Copy application
COPY . .
# Create non-root user
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
# Update PATH
ENV PATH=/root/.local/bin:$PATH
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:5000/health', timeout=2)"
# Run application
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]
Aplicación Go
# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Production stage
FROM alpine:latest
# Install ca-certificates for HTTPS
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy binary from builder
COPY --from=builder /app/main .
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080/health || exit 1
# Run binary
CMD ["./main"]
Aplicación Java Spring Boot
# Build stage
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
# Copy pom.xml
COPY pom.xml .
# Download dependencies
RUN mvn dependency:go-offline
# Copy source code
COPY src ./src
# Build application
RUN mvn clean package -DskipTests
# Production stage
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring
# Copy JAR from builder
COPY --from=builder /app/target/*.jar app.jar
# Change ownership
RUN chown spring:spring app.jar
# Switch to non-root user
USER spring
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080/actuator/health || exit 1
# Run application
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Sitio Estático con Nginx
# Build stage (optional, for building static assets)
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
# Copy custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy static files
COPY --from=builder /app/dist /usr/share/nginx/html
# Create non-root user (nginx already exists)
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /var/log/nginx && \
chown -R nginx:nginx /etc/nginx/conf.d
RUN touch /var/run/nginx.pid && \
chown -R nginx:nginx /var/run/nginx.pid
# Switch to non-root user
USER nginx
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080 || exit 1
# Start nginx
CMD ["nginx", "-g", "daemon off;"]
Técnicas de Optimización
Caché de Capas
# Bad - Changes to code invalidate all layers
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
# Good - Dependencies cached separately
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
Minimizar Capas
# Bad - Multiple layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get clean
# Good - Single layer
RUN apt-get update && \
apt-get install -y \
curl \
git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Usar .dockerignore
node_modules
.git
.env
*.log
.DS_Store
coverage
.vscode
Elegir Imágenes Base Más Pequeñas
# Large (900MB+)
FROM ubuntu:22.04
# Medium (200MB)
FROM node:18
# Small (50MB)
FROM node:18-slim
# Smallest (40MB)
FROM node:18-alpine
Eliminar Archivos Innecesarios
RUN apt-get update && \
apt-get install -y build-essential && \
# ... compile something ... && \
apt-get remove -y build-essential && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Mejores Prácticas de Seguridad
Ejecutar como Usuario No Root
# Create and use non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
# Or with Alpine
RUN addgroup -S appuser && adduser -S appuser -G appuser
USER appuser
Escanear en Busca de Vulnerabilidades
# Using Docker Scout
docker scout cves my-app:latest
# Using Trivy
trivy image my-app:latest
# Using Snyk
snyk container test my-app:latest
Usar Etiquetas Específicas
# Bad - unpredictable
FROM node:latest
# Good - predictable and secure
FROM node:18.17.0-alpine3.18
Minimizar Superficie de Ataque
# Use distroless images for minimal attack surface
FROM gcr.io/distroless/nodejs:18
# Or minimal Alpine
FROM alpine:3.18
No Incluir Secretos
# Bad - secrets in image
ENV API_KEY=secret123
# Good - pass at runtime
# docker run -e API_KEY=secret123 my-app
Usar COPY en Lugar de ADD
# Preferred
COPY app.py .
# Avoid unless needed
ADD archive.tar.gz /app/
Verificar Descargas
# Verify checksum
RUN curl -fsSL https://example.com/file -o /tmp/file && \
echo "expected_hash /tmp/file" | sha256sum -c -
Solución de Problemas
La Construcción Falla en el Comando RUN
# Show build output
docker build --progress=plain .
# Debug specific layer
docker run -it <layer_id> sh
Tamaño de Imagen Demasiado Grande
# Check layer sizes
docker history my-app:latest
# Analyze with dive
dive my-app:latest
El Caché No Funciona
# Force rebuild without cache
docker build --no-cache .
# Check what changed
docker build --progress=plain .
Errores de Permiso Denegado
# Ensure proper ownership
COPY --chown=appuser:appuser app.py /app/
# Or fix after copy
RUN chown -R appuser:appuser /app
Conclusión
Escribir Dockerfiles efectivos es fundamental para el éxito de la contenedorización. Esta guía cubrió todo desde la sintaxis básica hasta construcciones multi-etapa avanzadas y prácticas de seguridad.
Conclusiones Clave
- Optimización de Capas: Ordena las instrucciones de menos a más frecuentemente cambiantes
- Construcciones Multi-Etapa: Separa entornos de construcción y tiempo de ejecución
- Seguridad Primero: Siempre ejecuta como no root, usa etiquetas específicas, escanea vulnerabilidades
- El Tamaño Importa: Usa imágenes Alpine, minimiza capas, aprovecha .dockerignore
- Mejores Prácticas: Sigue convenciones, documenta con LABEL, implementa verificaciones de salud
Lista de Verificación de Dockerfile
- Usar etiquetas de versión específicas para imágenes base
- Implementar construcciones multi-etapa para lenguajes compilados
- Ejecutar contenedores como usuario no root
- Agregar instrucción de verificación de salud
- Crear archivo .dockerignore
- Minimizar número de capas
- Limpiar en el mismo comando RUN
- Usar COPY en lugar de ADD
- Establecer WORKDIR apropiado
- Documentar puertos expuestos
- Agregar etiquetas de metadatos
- Implementar logging apropiado
Próximos Pasos
- Practica: Construye Dockerfiles para tus aplicaciones
- Optimiza: Usa dive para analizar y reducir el tamaño de la imagen
- Asegura: Implementa escaneo de vulnerabilidades en CI/CD
- Documenta: Agrega metadatos LABEL completos
- Prueba: Crea etapas de prueba en construcciones multi-etapa
- Automatiza: Integra construcciones en pipelines CI/CD
- Monitorea: Rastrea tamaños de imagen y tiempos de construcción
Con estas mejores prácticas de Dockerfile, estás equipado para crear imágenes de contenedor eficientes, seguras y mantenibles para cualquier stack de aplicaciones.


