Hardening de contenedores Docker: guía práctica para producción
Cómo ejecutar contenedores Docker con menos riesgo: usuarios sin privilegios, sistema de ficheros de solo lectura, límites, redes, secretos e imágenes seguras.

Los contenedores aíslan aplicaciones, pero no son una máquina virtual: comparten el kernel del sistema anfitrión. Un contenedor mal configurado puede dar a un atacante mucho más acceso del que imaginas. La buena noticia es que la mayoría de los riesgos se eliminan con unas pocas opciones que caben en un docker-compose.yml.
1. No ejecutes como root
Por defecto, el proceso de un contenedor se ejecuta como root dentro del contenedor. Crea un usuario en la imagen y úsalo:
FROM python:3.13-slim
RUN useradd --uid 1000 --create-home app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["python", "main.py"]
Si la aplicación necesita escuchar en un puerto bajo (como el 80), usa un puerto alto dentro del contenedor y mapéalo fuera.
2. Quita capacidades y bloquea la escalada de privilegios
Los contenedores reciben algunas capacidades del kernel de Linux que la mayoría de las aplicaciones no necesitan. Quítalas todas y añade solo las imprescindibles:
services:
app:
cap_drop: [ALL]
security_opt:
- "no-new-privileges:true"
no-new-privileges impide que un proceso gane privilegios, por ejemplo mediante binarios con el bit setuid.
Nunca uses privileged: true salvo que sepas exactamente por qué, y casi nunca es necesario.
3. Sistema de ficheros de solo lectura
Si la aplicación no necesita escribir en su propio sistema de ficheros, móntalo en solo lectura. Un atacante no podrá descargar herramientas ni modificar el código:
read_only: true
tmpfs: [/tmp]
volumes:
- ./data:/app/data # solo lo que de verdad necesita escribir
4. Limita recursos
Un contenedor comprometido o con un fallo no debe poder dejar sin memoria ni CPU al resto del servidor:
mem_limit: 512m
cpus: "1.0"
pids_limit: 200
En algunos sistemas, como Raspberry Pi OS, el kernel arranca con el control de memoria de cgroups desactivado y el límite de memoria no se aplica. Comprueba con docker info que no aparece el aviso “No memory limit support”.
5. Cuidado con los puertos publicados
Docker manipula las reglas de iptables y los puertos publicados con ports: pueden quedar accesibles desde internet aunque tu firewall (por ejemplo, UFW) diga lo contrario. Buenas prácticas:
- Publica solo lo que deba ser accesible desde fuera.
- Para servicios internos, publica solo en local:
"127.0.0.1:8080:8080". - Si los contenedores solo hablan entre sí, no publiques puertos: usa una red de Docker propia.
6. Nunca montes el socket de Docker
Montar /var/run/docker.sock dentro de un contenedor equivale a darle control total del servidor. Si una herramienta lo exige, por ejemplo un panel de gestión, valora un proxy del socket que limite las operaciones permitidas, y trata ese contenedor como crítico.
7. Imágenes pequeñas, fijadas y analizadas
- Usa imágenes base mínimas (
-slim,alpine, distroless): menos paquetes, menos vulnerabilidades. - Fija las versiones (
python:3.13-slim, nolatest) o, mejor aún, el digest de la imagen. - Usa imágenes oficiales o de editores verificados.
- Analiza las imágenes en busca de vulnerabilidades con herramientas como Trivy o Docker Scout, idealmente en tu proceso de integración continua.
- Reconstruye con regularidad para incorporar los parches de la imagen base.
8. Secretos fuera de la imagen
- Nunca copies contraseñas ni claves dentro de la imagen: quedan en sus capas aunque luego borres el fichero.
- Usa un fichero
.envfuera del control de versiones, con permisos600, o mejor los secretos de Docker o de tu orquestador. - Añade
.enval.gitignorey al.dockerignore.
9. El anfitrión también cuenta
- Mantén actualizados el sistema operativo y el motor de Docker.
- Restringe quién pertenece al grupo
docker: pertenecer a él equivale a serroot. - Valora el modo rootless de Docker o el remapeo de usuarios (
userns-remap) para reducir el impacto de una fuga del contenedor. - Centraliza los registros de los contenedores y limita su tamaño.
Ejemplo completo
services:
app:
image: miapp:1.4.2
user: "1000:1000"
read_only: true
tmpfs: [/tmp]
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
mem_limit: 512m
cpus: "1.0"
pids_limit: 200
env_file: .env
volumes:
- ./data:/app/data
ports:
- "127.0.0.1:8080:8080"
restart: unless-stopped
Preguntas frecuentes
¿Son los contenedores menos seguros que las máquinas virtuales? El aislamiento es más débil porque comparten kernel. Para cargas de terceros no confiables, las máquinas virtuales o los entornos de aislamiento reforzado son más adecuados.
¿Cómo compruebo mi configuración? El CIS Docker Benchmark y herramientas como docker-bench-security revisan automáticamente muchas de estas recomendaciones.