Los contenedores Docker normalmente no gestionan SSL por sí mismos; un proxy inverso al frente termina TLS y reenvía el tráfico descifrado al contenedor. Esta guía cubre los tres enfoques más comunes.
Enfoque 1: Proxy inverso Nginx con certificado manual
El enfoque más sencillo para despliegues pequeños. Obtén un certificado desde GetHTTPS y móntalo en un contenedor Nginx.
docker-compose.yml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs/fullchain.pem:/etc/ssl/fullchain.pem:ro
- ./certs/privkey.pem:/etc/ssl/privkey.pem:ro
depends_on:
- app
app:
image: your-app:latest
expose:
- "3000"
nginx.conf
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/ssl/fullchain.pem;
ssl_certificate_key /etc/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://app:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}
Para renovar: Reemplaza los archivos en ./certs/ con los nuevos de GetHTTPS y ejecuta docker compose exec nginx nginx -s reload.
Enfoque 2: Traefik con Let’s Encrypt automático
Traefik es un proxy inverso diseñado para contenedores. Puede obtener y renovar certificados de Let’s Encrypt automáticamente.
docker-compose.yml
services:
traefik:
image: traefik:v3.0
command:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-acme:/acme
app:
image: your-app:latest
labels:
- "traefik.http.routers.app.rule=Host(`yourdomain.com`)"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
- "traefik.http.services.app.loadbalancer.server.port=3000"
volumes:
traefik-acme:
Traefik se encarga de todo: emisión del certificado, renovación y terminación HTTPS. No se necesita gestión manual de certificados.
Enfoque 3: Caddy (HTTPS sin configuración)
Caddy obtiene y renueva certificados de Let’s Encrypt automáticamente sin necesidad de configuración.
docker-compose.yml
services:
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
depends_on:
- app
app:
image: your-app:latest
expose:
- "3000"
volumes:
caddy-data:
Caddyfile
yourdomain.com {
reverse_proxy app:3000
}
Esa es toda la configuración. Caddy obtiene un certificado de Let’s Encrypt y lo renueva automáticamente.
¿Qué enfoque elegir?
| Nginx + certificado manual | Traefik | Caddy | |
|---|---|---|---|
| Configuración | Media (config + archivos de cert) | Media (labels) | Mínima |
| Renovación automática | No (manual) | Sí | Sí |
| Flexibilidad | Alta | Alta | Media |
| Curva de aprendizaje | Baja (familiar) | Media | Baja |
| Mejor para | Despliegues pequeños, control total | Contenedores dinámicos, microservicios | Sitios simples, configuración mínima |
Renovar certificados en Docker
Enfoque 1 (Nginx + manual):
# Replace cert files in your local ./certs/ directory
cp new-fullchain.pem ./certs/fullchain.pem
cp new-privkey.pem ./certs/privkey.pem
# Reload Nginx inside the container (no restart needed)
docker compose exec nginx nginx -s reload
Enfoque 2 (Traefik) y 3 (Caddy):
Automático: gestionan la renovación internamente. Los certificados se almacenan en volúmenes Docker (traefik-acme o caddy-data) y persisten entre reinicios del contenedor.
Docker Swarm: uso de secrets
Para despliegues con Docker Swarm, usa Docker secrets en lugar de montar archivos de certificado directamente:
# Create secrets from your cert files
docker secret create ssl_cert fullchain.pem
docker secret create ssl_key privkey.pem
# In docker-compose.yml (deploy mode)
services:
nginx:
image: nginx:alpine
secrets:
- ssl_cert
- ssl_key
configs:
- source: nginx_conf
target: /etc/nginx/conf.d/default.conf
secrets:
ssl_cert:
external: true
ssl_key:
external: true
Los secrets se montan en /run/secrets/ssl_cert y /run/secrets/ssl_key dentro del contenedor, cifrados en reposo y en tránsito.
Errores comunes
Incluir certificados dentro de las imágenes Docker
# ❌ NEVER do this
COPY fullchain.pem /etc/ssl/fullchain.pem
COPY privkey.pem /etc/ssl/privkey.pem
Los certificados expiran cada 90 días. Incluirlos en la imagen significa reconstruir y redesplegar cada 60 días. Monta siempre los certificados como volúmenes o secrets.
Olvidar exponer el puerto 80
El puerto 80 es necesario para:
- Redirección HTTP → HTTPS
- Desafío HTTP-01 de Let’s Encrypt (para la renovación automática de Traefik/Caddy)
ports:
- "80:80" # Don't forget this
- "443:443"
No persistir los datos ACME
Traefik y Caddy almacenan sus certificados de Let’s Encrypt y claves de cuenta en volúmenes. Sin persistencia, solicitan nuevos certificados en cada reinicio del contenedor y alcanzarán los límites de tasa:
volumes:
traefik-acme: # MUST be a named volume, not anonymous
caddy-data:
Preguntas frecuentes
¿Puede mi contenedor de aplicación manejar SSL directamente?
Es posible, pero no se recomienda. El patrón de proxy inverso (terminar TLS en el borde, reenviar HTTP plano internamente) es estándar porque: la aplicación no necesita saber sobre certificados, la renovación no requiere reiniciar la aplicación y se centraliza la configuración TLS.
¿Cómo manejo SSL para múltiples contenedores?
Con Traefik o Caddy, añade labels/configuración para cada servicio; obtendrán certificados separados automáticamente. Con Nginx, añade múltiples bloques server en la configuración.
¿Debo usar GetHTTPS o el ACME integrado de Traefik?
Usa el ACME integrado de Traefik/Caddy para producción, ya que gestiona la renovación automáticamente. Usa GetHTTPS cuando necesites un certificado para el enfoque de proxy inverso Nginx (Enfoque 1) o para entornos donde el contenedor no tiene acceso directo a internet para los desafíos ACME.
¿Cómo monto certificados de forma segura en Docker?
Monta como solo lectura (:ro), usa Docker secrets para la clave privada en modo Swarm y asegúrate de que solo el contenedor del proxy inverso tenga acceso a los archivos del certificado. Nunca incluyas certificados dentro de una imagen Docker, ya que expirarán y tendrías que reconstruir.
¿Puedo usar certificados de GetHTTPS con Kubernetes?
Sí. Crea un secret TLS de Kubernetes a partir de los archivos de certificado de GetHTTPS:
kubectl create secret tls my-tls-cert --cert=fullchain.pem --key=privkey.pem
Luego referéncialo en tu recurso Ingress. Para renovación automatizada en Kubernetes, considera cert-manager con un ClusterIssuer de Let’s Encrypt.