Docker facilita la contenedorización de aplicaciones, pero la configuración predeterminada está lejos de ser segura. Contenedores ejecutándose como root, imágenes infladas con paquetes innecesarios y límites de recursos faltantes representan riesgos serios. En este tutorial práctico, recorremos cada paso para hacer tus contenedores Docker listos para producción y seguros.
Imágenes base mínimas y builds multi-stage
La primera regla de seguridad de contenedores: minimiza la superficie de ataque. Usa Alpine Linux (5 MB) o imágenes distroless en lugar de imágenes completas de Ubuntu o Debian. Cada paquete extra en tu imagen es una vulnerabilidad potencial. Con builds multi-stage, compilas tu aplicación en una etapa builder con todas las herramientas de desarrollo, y copias solo el artefacto final a una imagen runtime mínima.
Escanea tus imágenes regularmente con herramientas como Trivy, Snyk o Docker Scout. Integra esto en tu pipeline CI/CD para que las imágenes vulnerables nunca lleguen a producción. Fija las versiones de tus imágenes base con un hash digest específico en lugar de tags mutables como :latest para prevenir riesgos de supply chain.
# Dockerfile óptimo con build multi-stage
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Stage 2: Runtime (imagen mínima)
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
# Ejecutar como usuario non-root (uid 1000)
USER 1000
EXPOSE 3000
CMD ["dist/server.js"]
Usuarios non-root, sistemas de archivos de solo lectura y límites de recursos
Los contenedores se ejecutan como root por defecto — un riesgo de seguridad enorme. Si un atacante escapa del contenedor, tiene acceso root en el host. Siempre usa la directiva USER en tu Dockerfile para ejecutar como usuario non-root. Crea un usuario dedicado o usa el usuario integrado node, www-data o nobody.
Ejecuta contenedores con un sistema de archivos root de solo lectura mediante --read-only. Solo monta directorios específicos como escribibles donde tu aplicación necesite escribir archivos temporales. También establece siempre límites de recursos con --memory y --cpus para evitar que un contenedor comprometido agote todo el sistema. Elimina todas las capabilities de Linux con --cap-drop ALL y solo agrega las estrictamente necesarias.
- Usa distroless o Alpine como imagen base — evita imágenes infladas
- Siempre ejecuta como non-root con la directiva USER
- Habilita el sistema de archivos root de solo lectura con el flag --read-only
- Establece límites de memoria y CPU para cada contenedor
- Elimina todas las capabilities con --cap-drop ALL y solo agrega las necesarias
- Escanea imágenes con Trivy o Snyk en tu pipeline CI/CD
Seguridad runtime y secrets en Docker
La seguridad en tiempo de build es solo la mitad de la historia. Usa el perfil seccomp de Docker para restringir syscalls y AppArmor o SELinux para control de acceso obligatorio. También habilita --no-new-privileges para evitar que los procesos en el contenedor escalen sus privilegios después de iniciar.
Nunca almacenes secrets en imágenes Docker o variables de entorno. Usa Docker Secrets (en Swarm) o gestores de secretos externos como HashiCorp Vault. Para desarrollo, puedes usar Docker Compose con un archivo .env que esté en .gitignore. En producción, monta secrets como archivos en un volumen tmpfs con permisos restrictivos (0400). No olvides asegurar tu propio daemon Docker: habilita TLS, restringe el acceso a la API y mantén Docker actualizado.
Consejo Pro
Ejecuta docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image <tu-imagen> en tus imágenes de producción ahora. Probablemente encontrarás vulnerabilidades críticas en tus imágenes base. Luego cambia a una imagen base distroless o Alpine, agrega USER 1000 a cada Dockerfile, y habrás eliminado el 80% de las vulnerabilidades de contenedores más comunes en menos de una hora.
Compartir este artículo