Docker y Contenedores
Contenedores, imágenes, buenas prácticas de Dockerfile y multi-stage builds para empaquetar y distribuir aplicaciones.
¿Qué es Docker?
Docker es una plataforma que permite empaquetar aplicaciones junto con todas sus dependencias en unidades aisladas llamadas contenedores. Un contenedor incluye el código, el runtime, las librerías y la configuración necesaria para que la aplicación funcione de forma idéntica en cualquier máquina.
A diferencia de las máquinas virtuales, los contenedores comparten el kernel del sistema operativo host, lo que los hace mucho más livianos y rápidos de iniciar.
Conceptos fundamentales
Imagen
Una imagen es un paquete inmutable que contiene todo lo necesario para ejecutar una aplicación. Se construye a partir de un Dockerfile y se almacena en un registry (como Docker Hub, ECR o GCR).
Las imágenes se componen de capas (layers). Cada instrucción del Dockerfile crea una nueva capa. Docker cachea las capas que no cambian, lo que acelera las builds posteriores.
Contenedor
Un contenedor es una instancia en ejecución de una imagen. Es efímero por naturaleza — cuando se detiene, su estado interno se pierde (a menos que se usen volúmenes).
Registry
Un registry es un repositorio de imágenes. Los más comunes son:
- Docker Hub: registry público por defecto
- Amazon ECR: registry privado en AWS
- Google GCR / Artifact Registry: registry en GCP
- GitHub Container Registry: integrado con GitHub
Dockerfile: buenas prácticas
El Dockerfile define cómo se construye una imagen. Un Dockerfile bien escrito produce imágenes pequeñas, seguras y rápidas de construir.
Ejemplo básico
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "dist/server.js"]
Buenas prácticas
- Usar imágenes base pequeñas: preferir variantes
alpineoslimsobre las imágenes completas - Ordenar instrucciones por frecuencia de cambio: las capas que cambian menos van primero para aprovechar el cache
- Copiar dependencias antes que el código:
COPY package*.jsonantes deCOPY .para cachearnpm install - No ejecutar como root: crear un usuario no privilegiado con
USER - Usar
.dockerignore: excluirnode_modules,.git, archivos de test y documentación - Minimizar el número de capas: combinar comandos
RUNrelacionados con&& - No instalar herramientas innecesarias: cada paquete extra aumenta la superficie de ataque
Ejemplo con usuario no root
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --only=production
COPY --chown=appuser:appgroup . .
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]
Multi-stage builds
Los multi-stage builds permiten usar múltiples etapas en un solo Dockerfile. Esto es especialmente útil para separar la etapa de compilación de la etapa de ejecución, produciendo imágenes finales mucho más pequeñas.
¿Por qué usar multi-stage?
- La imagen final no incluye herramientas de compilación (TypeScript compiler, build tools, etc.)
- Se reduce drásticamente el tamaño de la imagen
- Se minimiza la superficie de ataque en producción
Ejemplo: aplicación Node.js con TypeScript
# Etapa 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Etapa 2: Producción
FROM node:20-alpine AS production
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]
En este ejemplo, la imagen final solo contiene el código compilado y las dependencias de producción — sin TypeScript, sin devDependencies, sin código fuente.
Docker Compose para desarrollo
Docker Compose permite definir y ejecutar aplicaciones multi-contenedor. Es ideal para el entorno de desarrollo local:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/myapp
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: password
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:
Seguridad en contenedores
- Escanear imágenes: usar herramientas como Trivy, Snyk o Docker Scout para detectar vulnerabilidades
- Actualizar imágenes base: mantener las imágenes base actualizadas con parches de seguridad
- No almacenar secretos en imágenes: usar variables de entorno o gestores de secretos en runtime
- Limitar recursos: configurar límites de CPU y memoria para evitar que un contenedor consuma todos los recursos del host
- Usar imágenes firmadas: verificar la procedencia de las imágenes con Docker Content Trust
Resumen
Docker simplifica el empaquetado y distribución de aplicaciones al garantizar que el mismo artefacto funciona en cualquier entorno. Las claves son: imágenes pequeñas con multi-stage builds, buenas prácticas de Dockerfile, seguridad desde el diseño y Docker Compose para desarrollo local.