Seguridad de credenciales en Docker y migración a Docker Swarm
Docker ofrece varios mecanismos para evitar almacenar credenciales sensibles en texto plano dentro de docker-compose.yml o archivos .env.
Los principales son Docker Secrets, configs protegidos, e integración con secret managers externos.
1. Docker Secrets (recomendado)
Es el mecanismo nativo diseñado específicamente para credenciales (contraseñas, tokens, claves privadas).
Los secretos:
- No se almacenan en el
compose.yml. - Se montan dentro del contenedor como archivos temporales en
/run/secrets/.... - Tienen permisos restringidos (solo lectura).
- No aparecen en variables de entorno visibles en
docker inspect.
Ejemplo:
Crear el secreto:
echo "mypassword" | docker secret create db_password -
En docker-compose.yml (modo swarm o compose v3+ compatible):
services:
db:
image: postgres
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
external: true
La aplicación lee la contraseña desde el archivo:
/run/secrets/db_password
Ventaja clave: evita exposición en variables de entorno y en el repositorio.
2. Archivos montados con permisos restringidos
Si no usas Swarm o Secrets, puedes montar un archivo local fuera del repositorio:
services:
app:
volumes:
- /secure/path/password.txt:/run/secrets/password:ro
Luego la app lee:
/run/secrets/password
No es tan robusto como Docker Secrets, pero reduce exposición.
3. Secret managers externos
En entornos profesionales se integra Docker con:
- HashiCorp Vault
- AWS Secrets Manager
- Azure Key Vault
- GCP Secret Manager
El contenedor obtiene el secreto dinámicamente en runtime mediante sidecars o init containers.
Esto elimina completamente los secretos del host y del repositorio.
4. Evitar variables de entorno sensibles
Las variables de entorno:
- aparecen en
docker inspect - pueden verse en dumps de procesos
- pueden filtrarse en logs
Por eso se consideran menos seguras que secrets montados como archivo.
Recomendación práctica
Arquitectura típica segura:
- Desarrollo local: archivo
.envfuera del repositorio +.gitignore - Producción: Docker Secrets o Vault
- Aplicaciones: soporte para
*_FILE(ej.PASSWORD_FILE)
Migración de Docker Compose a Docker Swarm
Diferencias clave entre Compose y Swarm
Antes de migrar es importante entender el cambio de modelo:
| Docker Compose | Docker Swarm |
|---|---|
| Orquestación local | Orquestación cluster |
| docker compose up | docker stack deploy |
| No gestiona secretos nativamente | Docker Secrets nativos |
| Escalado manual limitado | Escalado automático por servicio |
| Sin scheduler distribuido | Scheduler distribuido |
Requisitos previos
- Docker instalado en todos los nodos
- Abrir puertos necesarios entre nodos:
- TCP 2377 (cluster management)
- TCP/UDP 7946 (node communication)
- UDP 4789 (overlay network)
Inicializar el cluster
En el nodo manager:
docker swarm init
Obtendrás un comando para unir workers:
docker swarm join --token <token> <ip-manager>:2377
Ejecutarlo en los demás nodos. Verificar:
docker node ls
Adaptar docker-compose.yml a Swarm
Swarm usa version 3+ del schema. Ejemplo base:
version: "3.9"
services:
api:
image: myapi:latest
deploy:
replicas: 3
restart_policy:
condition: on-failure
resources:
limits:
cpus: "0.5"
memory: 512M
ports:
- "80:8080"
IMPORTANTE: la sección deploy solo funciona en Swarm.
Migrar variables sensibles a Docker Secrets
echo "mypassword" | docker secret create db_password -
En docker-compose.yml Swarm:
services:
db:
image: postgres
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
external: true
Redes overlay
Swarm usa redes overlay compartidas:
docker network create --driver overlay backend
En compose:
networks:
backend:
external: true
Desplegar stack
En lugar de docker compose up:
docker stack deploy -c docker-compose.yml mystack
Ver servicios:
docker stack services mystack
Ver tareas:
docker stack ps mystack
Escalado de servicios
Escalar manualmente:
docker service scale mystack_api=5
O modificar:
deploy:
replicas: 5
y redeploy:
docker stack deploy -c docker-compose.yml mystack
Actualizaciones rolling
Swarm permite rolling updates automáticos:
deploy:
update_config:
parallelism: 1
delay: 10s
Persistencia de datos
Los volúmenes deben existir en todos los nodos o usar:
- NFS
- GlusterFS
- Portworx
- drivers cloud
Ejemplo:
volumes:
dbdata:
driver: local
Eliminación del stack
docker stack rm mystack
Ejemplo completo para producción (API + DB + Secrets)
version: "3.9"
services:
api:
image: myapi:latest
networks:
- backend
deploy:
replicas: 3
restart_policy:
condition: on-failure
db:
image: postgres:16
networks:
- backend
volumes:
- dbdata:/var/lib/postgresql/data
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
networks:
backend:
driver: overlay
volumes:
dbdata:
secrets:
db_password:
external: true
Despliegue:
docker stack deploy -c docker-compose.yml production
Estrategia profesional recomendada de migración (muy usada)
- Migrar compose a version 3.9
- Sustituir
.envsensibles → Docker Secrets - Crear redes overlay
- Añadir sección
deploy - Inicializar swarm
- Deploy stack
- Activar rolling updates
- Añadir storage distribuido
En Swarm los contenedores pueden ejecutarse en cualquier nodo por tanto los archivos locales con credenciales dejan de ser válidos por eso Secrets es obligatorio en producción
Ejemplos cde estructuras completas seguras para producción
Ejemplo 1
- Una API y una base de datos
- Uso de Docker Secrets en lugar de variables de entorno sensibles
- Red overlay
- Volúmenes para persistencia
- Sección deploy para Swarm (replicas, límites de recursos, restart policy)
version: "3.9"
services:
api:
image: myapi:latest
networks:
- backend
deploy:
replicas: 3
restart_policy:
condition: on-failure
resources:
limits:
cpus: "0.5"
memory: 512M
update_config:
parallelism: 1
delay: 10s
order: start-first
ports:
- "80:8080"
environment:
# Usamos *_FILE para leer secretos desde archivos
DATABASE_PASSWORD_FILE: /run/secrets/db_password
db:
image: postgres:16
networks:
- backend
volumes:
- dbdata:/var/lib/postgresql/data
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
networks:
backend:
driver: overlay
volumes:
dbdata:
secrets:
db_password:
external: true
Cómo usarla en producción
- Crear el secreto antes de desplegar:
echo "SuperSecreta123" | docker secret create db_password - - Desplegar el stack en Swarm:
docker stack deploy -c docker-compose.yml production - Ver servicios y tareas:
docker stack services production docker stack ps production - Escalar la API si hace falta:
docker service scale production_api=5
Características de seguridad y producción incluidas:
- Contraseñas nunca en texto plano ni en
.env - Uso de Docker Secrets
(/run/secrets/...) - Red overlay cifrada opcional
- Persistencia de DB con volúmenes
- Rolling updates con
update_config - Recursos limitados por contenedor
- Política de reinicio
on-failure
Ejemplo 2. Versión extendida
Este ejemplo es una versión extendida del anterior docker-compose que incluye reverse proxy con TLS automático (Traefik o Nginx), logging centralizado y monitorización, lista para producción completa.
- API + DB + Secrets
- Reverse proxy con TLS automático usando Traefik
- Logging centralizado con JSON logs
- Rolling updates y límites de recursos
- Red overlay segura
- Persistencia de datos
version: "3.9"
services:
# Reverse Proxy con Traefik para TLS automático
traefik:
image: traefik:v2.11
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
- "--certificatesresolvers.myresolver.acme.email=admin@midominio.com"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_letsencrypt:/letsencrypt
networks:
- frontend
deploy:
replicas: 1
placement:
constraints:
- node.role == manager
# API
api:
image: myapi:latest
networks:
- frontend
- backend
deploy:
replicas: 3
restart_policy:
condition: on-failure
resources:
limits:
cpus: "0.5"
memory: 512M
update_config:
parallelism: 1
delay: 10s
order: start-first
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.midominio.com`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=myresolver"
environment:
DATABASE_PASSWORD_FILE: /run/secrets/db_password
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Base de datos
db:
image: postgres:16
networks:
- backend
volumes:
- dbdata:/var/lib/postgresql/data
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
deploy:
restart_policy:
condition: on-failure
resources:
limits:
cpus: "1"
memory: 1G"
networks:
frontend:
driver: overlay
backend:
driver: overlay
volumes:
dbdata:
traefik_letsencrypt:
secrets:
db_password:
external: true
El despliegue de esta arquitectura es idéntico al anterior
Ventajas de esta configuración
- Seguridad: Secrets para contraseñas, red overlay, TLS automático
- Disponibilidad: Rolling updates, múltiples replicas de API
- Persistencia: Volumen para la base de datos
- Exposición pública: Traefik maneja HTTPS automáticamente
- Logging centralizado: Archivos JSON rotados
- Recursos controlados: CPU y memoria limitadas para evitar DoS
Ejemplo 3. Versión final con monitorización
Versión final más completa, agregando monitorización con Prometheus, Grafana y alertas, lista para un entorno de producción real.
Esta versión final completa lista para producción incluye:
- API + DB + Secrets
- Logging centralizado
- Persistencia de datos
- Monitorización con Prometheus + Grafana
- Alertas básicas
- Redes overlay cifradas
- Rolling updates y límites de recursos
version: "3.9"
services:
# Reverse Proxy con Traefik para TLS automático
traefik:
image: traefik:v2.11
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
- "--certificatesresolvers.myresolver.acme.email=admin@midominio.com"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_letsencrypt:/letsencrypt
networks:
- frontend
deploy:
replicas: 1
placement:
constraints:
- node.role == manager
# API
api:
image: myapi:latest
networks:
- frontend
- backend
deploy:
replicas: 3
restart_policy:
condition: on-failure
resources:
limits:
cpus: "0.5"
memory: 512M
update_config:
parallelism: 1
delay: 10s
order: start-first
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.midominio.com`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=myresolver"
environment:
DATABASE_PASSWORD_FILE: /run/secrets/db_password
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Base de datos
db:
image: postgres:16
networks:
- backend
volumes:
- dbdata:/var/lib/postgresql/data
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
deploy:
restart_policy:
condition: on-failure
resources:
limits:
cpus: "1"
memory: 1G
# Monitorización con Prometheus
prometheus:
image: prom/prometheus:latest
networks:
- monitoring
volumes:
- prometheus_data:/prometheus
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
deploy:
replicas: 1
restart_policy:
condition: on-failure
ports:
- "9090:9090"
# Dashboard Grafana
grafana:
image: grafana/grafana:latest
networks:
- monitoring
volumes:
- grafana_data:/var/lib/grafana
deploy:
replicas: 1
restart_policy:
condition: on-failure
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin123
networks:
frontend:
driver: overlay
backend:
driver: overlay
monitoring:
driver: overlay
volumes:
dbdata:
traefik_letsencrypt:
prometheus_data:
grafana_data:
secrets:
db_password:
external: true
El despliegue es igual al de la primera versión.
Características de esta versión
- Seguridad: Secrets para contraseñas, TLS automático con Traefik, redes overlay
- Disponibilidad: Múltiples replicas de API, rolling updates
- Persistencia: Volúmenes para DB, Grafana y Prometheus
- Monitorización: Prometheus recolecta métricas, Grafana dashboard listo para visualización
- Logging centralizado: Archivos JSON rotados
- Alertas: Configurables desde Prometheus
- Recursos controlados: CPU y memoria limitadas por contenedor
Los archivos docker-compose mostrados no despliegan una aplicación concreta que exista públicamente, sino que es un ejemplo de arquitectura de producción segura. Su objetivo es ilustrar cómo estructurar servicios críticos en Swarm usando buenas prácticas de seguridad y despliegue.
En términos de funcionalidad concreta:
Servicios que incluye
traefik- Actúa como reverse proxy y gestor de TLS automático.
- Dirige el tráfico HTTP/HTTPS a la API según el host (api.midominio.com).
- Gestiona certificados Let’s Encrypt automáticamente.
api- Representa tu aplicación de negocio (por ejemplo, un servicio web REST).
- La imagen myapi:latest es un placeholder, aquí pondrías tu propia aplicación Dockerizada.
- Se conecta a la base de datos y recibe las credenciales vía Docker Secret.
db (Postgres 16)- Base de datos PostgreSQL para almacenar datos de la API. -Sus credenciales se manejan con Docker Secrets, nunca en texto plano.
- Persistencia de datos mediante volumen dbdata.
-
prometheusRecolecta métricas de contenedores y servicios del cluster para monitorización. grafana- Dashboard de visualización de métricas de Prometheus.
- Permite crear gráficos, paneles y alertas de forma centralizada.
Arquitectura general
[Internet] --> [Traefik] --> [API] --> [DB]
\
--> [Prometheus + Grafana]
- Traefik maneja la entrada de tráfico y TLS.
- API es el backend real de negocio.
- DB almacena datos de forma segura.
- Prometheus + Grafana permiten monitorizar la salud y métricas de toda la arquitectura.
Actualmente la imagen myapi:latest no existe. Para usar este docker-compose en un entorno real debes:
- Crear tu propia imagen Docker de la aplicación que quieras desplegar
(docker build -t myapi:latest .). - Mantener el resto de la arquitectura (Traefik, DB, Prometheus, Grafana) tal cual para producción.