Buenas Prácticas de Seguridad en Docker

Lectura
25 min~7 min lectura
CONCEPTO CLAVE: La seguridad en Docker no es una característica opcional, es una responsabilidad compartida entre el desarrollador, el equipo de operaciones y la organización. Cada contenedor debe diseñarse con el principio de "menor privilegio posible" (least privilege principle).

¿Por qué es crítica la seguridad en Docker?

En un entorno de producción, los contenedores Docker pueden ser el vector de ataque más vulnerable si no se configuran correctamente. A diferencia de las máquinas virtuales tradicionales, los contenedores comparten el kernel del host, lo que significa que una vulnerabilidad en un contenedor podría afectar a todo el sistema.

📌 Estadística importante: Según el informe de Sysdig 2023, el 75% de las imágenes de contenedores en producción contienen vulnerabilidades conocidas, y el 50% tienen vulnerabilidades de alta severidad.

1. Selección y Construcción de Imágenes Seguras

Usar imágenes base mínimas

Una de las primeras líneas de defensa es elegir imágenes base que contengan únicamente lo necesario para tu aplicación.

💡 Recomendación: Prefiere imágenes oficiales minimizadas como alpine, distroless o slim sobre imágenes completas que incluyen herramientas innecesarias que pueden ser explotadas.
# ❌ Imagen pesada con múltiples herramientas
FROM ubuntu:latest

# ✅ Imagen mínima específica para Node.js
FROM node:18-alpine

# ✅ Aún mejor: imagen distroless de Google
FROM gcr.io/distroless/nodejs18-debian11

Utilizar imágenes firmadas (Content Trust)

Docker Hub y otros registries soportan Docker Content Trust para verificar la autenticidad de las imágenes.

# Habilitar Content Trust
export DOCKER_CONTENT_TRUST=1

# Solo se podrán extraer imágenes firmadas
docker pull mi-imagen:tag
⚠️ Advertencia: Si desactivas Content Trust temporalmente para testing, asegúrate de volver a activarlo antes de usar en producción. Las imágenes sin firma pueden haber sido manipuladas.

2. Gestión de Usuarios y Permisos

No ejecutar como root

Por defecto, Docker permite que los procesos dentro del contenedor se ejecuten como root. Esto es extremadamente peligroso si el contenedor es comprometido.

# ❌ Peligroso: ejecutando como root
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]

# ✅ Seguro: ejecutando como usuario no root
FROM node:18-alpine
WORKDIR /app

# Crear usuario sin privilegios
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

COPY package*.json ./
RUN npm install && chown -R appuser:appgroup /app
COPY --chown=appuser:appgroup . .

USER appuser
CMD ["node", "server.js"]
📌 Nota técnica: Aunque el usuario dentro del contenedor no sea root, si el usuario tiene UID 0 (root) mapeado al host, aún puede tener privilegios de root en el host. Usa la opción --user en docker run para mapear a un UID sin privilegios.

Configurar capacidades Linux mínimas

# Quitar todas las capacidades y solo dar las necesarias
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE \
  -u 1000:1000 \
  mi-aplicacion:seura

3. Gestión de Secretos y Datos Sensibles

No usar ENV para secretos

⚠️ Peligro crítico: Las variables de entorno en Docker son visibles para cualquier proceso que tenga acceso al contenedor. Además, persisten en las capas de la imagen. Nunca pongas contraseñas, API keys o tokens en variables de entorno.
# ❌ NUNCA hagas esto
FROM node:18-alpine
ENV DATABASE_PASSWORD=mi-contraseña-secreta
ENV API_KEY=sk-live-xxxxx

# ✅ Usar Docker Secrets (Swarm) o servicios externos
# Para Kubernetes: usar Sealed Secrets, HashiCorp Vault, AWS Secrets Manager

Usar volúmenes para datos sensibles

# Montar secretos desde archivos seguros
docker run -v /run/secrets/my_secret:/run/secrets/my_secret:ro \
  mi-aplicacion

# O usar Docker Secrets en Swarm
docker secret create db_password password.txt
docker service create --secret db_password mi-db
Ver más: Ejemplo completo de gestión de secretos
# docker-compose.yml con secrets externos
version: '3.8'

services:
  api:
    image: mi-api:production
    secrets:
      - db_password
      - api_key
    environment:
      - NODE_ENV=production
    volumes:
      - ./config:/config:ro
    
  db:
    image: postgres:15-alpine
    secrets:
      - db_password
    volumes:
      - postgres_data:/var/lib/postgresql/data

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    external: true

En este ejemplo, los secrets se montan como archivos en /run/secrets/ dentro del contenedor, nunca como variables de entorno.

4. Seguridad en Tiempo de Ejecución (Runtime)

Limitación de recursos

docker run \
  --memory="256m" \
  --memory-swap="256m" \
  --cpus="0.5" \
  --pids-limit=50 \
  --ulimits nproc=10:20 \
  mi-aplicacion
💡 Beneficio adicional: Limitar recursos previene ataques de denegación de servicio (DoS) donde un contenedor comprometido podría agotar los recursos del host.

Modo read-only del filesystem

docker run --read-only \
  --tmpfs /tmp \
  mi-aplicacion

Esto previene que malware escriba en el filesystem del contenedor, dificultando la persistencia de ataques.

5. Network Security

Segmentación de redes

# Crear redes isoladas para diferentes servicios
docker network create --driver bridge backend-net
docker network create --driver bridge frontend-net

# Los contenedores solo pueden comunicarse dentro de su red
docker run -d --network backend-net --name db postgres:15-alpine
docker run -d --network backend-net --name api mi-api
docker run -d --network frontend-net --name web mi-web
📌 Principio: Un contenedor de frontend no debería poder comunicarse directamente con la base de datos. Solo el API gateway o balancer debería tener acceso a la red interna.

Bloquear comunicaciones externas

# Evitar que los contenedores puedan cambiar el estado de la red del host
docker run --network none mi-servicio

# O denegar automáticamente el tráfico saliente (excepto lo explícitamente permitido)
ipTables -A FORWARD -i docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

6. Escaneo de Vulnerabilidades

Integrar escaneo en el pipeline CI/CD

# Usar Trivy para escanear imágenes
docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image mi-imagen:1.0.0

# Output en formato JSON para automatización
docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image --format json mi-imagen:1.0.0 > scan-results.json
  1. Configurar Trivy o Snyk en el pipeline de CI/CD como paso obligatorio
  2. Definir políticas de severidad (rechazar imágenes con vulnerabilities HIGH o CRITICAL)
  3. Configurar notificaciones automáticas al equipo de seguridad
  4. Programar rescaneos periódicos de imágenes en producción
  5. Mantener un inventario actualizado de todas las imágenes desplegadas

7. Hardening del Docker Daemon

# Configuración segura de daemon en /etc/docker/daemon.json
{
  "icc": false,
  "userland-proxy": false,
  "no-new-privileges": true,
  "seccomp-profile": "/etc/docker/seccomp.json",
  "apparmor-profile": "docker-default",
  "live-restore": true,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2"
}
⚠️ Importante: Después de modificar daemon.json, reinicia el servicio: sudo systemctl restart docker

8. Checklist de Seguridad para Producción

Práctica Estado Prioridad
Usar usuario no-root ✅ Implementado Crítica
Imágenes firmadas (Content Trust) ⏳ Pendiente Alta
Escaneo de vulnerabilidades en CI ✅ Implementado Crítica
Secrets externos (no ENV) ✅ Implementado Crítica
Filesystem read-only ⏳ Pendiente Alta
Limitación de recursos ✅ Implementado Media
Redes isoladas ✅ Implementado Alta
Healthchecks definidos ⏳ Pendiente Media

9. Healthchecks y Monitoreo

Los healthchecks no solo mejoran la disponibilidad, también permiten detectar comportamientos anómalos.

# Dockerfile con healthcheck
FROM node:18-alpine
WORKDIR /app

# ... copia de archivos ...

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "server.js"]
💡 Tip avanzado: Implementa logging centralizado (Elasticsearch, Loki, CloudWatch) para detectar patrones sospechosos como múltiples intentos de ejecución de comandos desde el contenedor.
"La seguridad no es un producto, es un proceso. No se trata de implementar una solución y olvidarse, sino de establecer prácticas continuas de revisión, actualización y mejora." — Bruce Schneier
🧠 Quiz: Buenas Prácticas de Seguridad

¿Cuál es la forma más segura de manejar contraseñas o API keys en contenedores Docker?

  • A) Usar variables de entorno ENV en el Dockerfile
  • B) Hardcodearlas directamente en el código fuente
  • C) Usar Docker Secrets o servicios externos de gestión de secretos (Vault, AWS Secrets Manager)
  • D) Guardarlas en comentarios dentro del código
Respuesta correcta: C) Los Docker Secrets o servicios externos como HashiCorp Vault o AWS Secrets Manager proporcionan una gestión segura de información sensible. Las variables de entorno (A) son visibles para cualquier proceso, y hardcodear secretos (B y D) es una vulnerabilidad crítica.
Ver más: Recursos adicionales de seguridad
  • CIS Docker Benchmark: Guía completa de hardening para Docker Engine
  • Docker Security Cheat Sheet: OWASP Docker Security Cheat Sheet
  • Herramientas recomendadas: Trivy, Snyk, Clair, Anchore, Falco
  • Cursos complementarios: Docker Security Essentials, Kubernetes Security

Mantén actualizado este checklist y revísalo trimestralmente para adaptarte a nuevas amenazas.

📌 Resumen final: La seguridad en Docker requiere un enfoque de defensa en profundidad. Cada capa cuenta: desde las imágenes que usas, pasando por cómo construyes tus contenedores, hasta cómo los ejecutas y monitoreas en producción. No existe la seguridad perfecta, pero implementar estas prácticas reduce drásticamente la superficie de ataque.