Docker 컨테이너는 일반적으로 SSL을 직접 처리하지 않습니다 — 앞단의 리버스 프록시가 TLS를 종료하고 복호화된 트래픽을 컨테이너로 전달합니다. 이 가이드에서는 가장 일반적인 세 가지 방법을 다룹니다.
방법 1: Nginx 리버스 프록시 + 수동 인증서
소규모 배포에 가장 간단한 방법입니다. GetHTTPS에서 인증서를 발급받아 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;
}
갱신 방법: ./certs/의 파일을 GetHTTPS에서 받은 새 파일로 교체한 후 docker compose exec nginx nginx -s reload를 실행합니다.
방법 2: Traefik + 자동 Let’s Encrypt
Traefik은 컨테이너 전용으로 설계된 리버스 프록시입니다. Let’s Encrypt 인증서를 자동으로 발급하고 갱신할 수 있습니다.
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이 인증서 발급, 갱신, HTTPS 종료를 모두 처리합니다. 수동 인증서 관리가 필요 없습니다.
방법 3: Caddy (제로 설정 HTTPS)
Caddy는 설정 없이 Let’s Encrypt 인증서를 자동으로 발급하고 갱신합니다.
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
}
설정은 이것이 전부입니다. Caddy가 Let’s Encrypt 인증서를 발급받고 자동으로 갱신합니다.
어떤 방법을 선택해야 하나요?
| Nginx + 수동 인증서 | Traefik | Caddy | |
|---|---|---|---|
| 설정 | 중간 (설정 + 인증서 파일) | 중간 (라벨) | 최소 |
| 자동 갱신 | ❌ (수동) | ✅ | ✅ |
| 유연성 | 높음 | 높음 | 중간 |
| 학습 곡선 | 낮음 (익숙함) | 중간 | 낮음 |
| 적합한 용도 | 소규모 배포, 완전한 제어 | 동적 컨테이너, 마이크로서비스 | 간단한 사이트, 최소 설정 |
Docker에서 인증서 갱신
방법 1 (Nginx + 수동):
# 로컬 ./certs/ 디렉터리의 인증서 파일 교체
cp new-fullchain.pem ./certs/fullchain.pem
cp new-privkey.pem ./certs/privkey.pem
# 컨테이너 내부의 Nginx 리로드 (재시작 불필요)
docker compose exec nginx nginx -s reload
방법 2 (Traefik) 및 방법 3 (Caddy):
자동 처리 — 내부적으로 갱신을 관리합니다. 인증서는 Docker 볼륨(traefik-acme 또는 caddy-data)에 저장되며 컨테이너 재시작 시에도 유지됩니다.
Docker Swarm: 시크릿 사용
Docker Swarm 배포의 경우, 인증서 파일을 바인드 마운트하는 대신 Docker 시크릿을 사용합니다:
# 인증서 파일로 시크릿 생성
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
시크릿은 컨테이너 내부의 /run/secrets/ssl_cert와 /run/secrets/ssl_key에 마운트되며, 저장 시와 전송 시 모두 암호화됩니다.
흔한 실수
인증서를 Docker 이미지에 포함시키기
# ❌ 절대 이렇게 하지 마세요
COPY fullchain.pem /etc/ssl/fullchain.pem
COPY privkey.pem /etc/ssl/privkey.pem
인증서는 90일마다 만료됩니다. 이미지에 포함시키면 60일마다 이미지를 다시 빌드하고 재배포해야 합니다. 항상 볼륨이나 시크릿으로 인증서를 마운트하세요.
포트 80 노출을 잊는 경우
포트 80은 다음에 필요합니다:
- HTTP → HTTPS 리다이렉트
- Let’s Encrypt HTTP-01 챌린지 (Traefik/Caddy 자동 갱신용)
ports:
- "80:80" # 이것을 잊지 마세요
- "443:443"
ACME 데이터를 영속화하지 않는 경우
Traefik과 Caddy는 Let’s Encrypt 인증서와 계정 키를 볼륨에 저장합니다. 영속화 없이는 컨테이너 재시작마다 인증서를 다시 요청하게 되어 레이트 리밋에 걸립니다:
volumes:
traefik-acme: # 반드시 네임드 볼륨이어야 합니다. 익명 볼륨은 안 됩니다
caddy-data:
자주 묻는 질문
앱 컨테이너에서 직접 SSL을 처리할 수 있나요?
가능하지만 권장하지 않습니다. 리버스 프록시 패턴(엣지에서 TLS를 종료하고 내부적으로 평문 HTTP를 전달)이 표준인 이유는: 앱이 인증서를 알 필요가 없고, 갱신 시 앱 재시작이 불필요하며, TLS 설정을 중앙화할 수 있기 때문입니다.
여러 컨테이너에 대한 SSL은 어떻게 처리하나요?
Traefik이나 Caddy를 사용하면 각 서비스에 라벨/설정을 추가하기만 하면 자동으로 별도의 인증서를 받습니다. Nginx를 사용하는 경우 설정에 여러 server 블록을 추가합니다.
GetHTTPS와 Traefik의 내장 ACME 중 어떤 것을 사용해야 하나요?
프로덕션에서는 Traefik/Caddy의 내장 ACME를 사용하세요 — 갱신을 자동으로 처리합니다. GetHTTPS는 Nginx 리버스 프록시 방식(방법 1)이나 ACME 챌린지를 위한 직접 인터넷 접근이 없는 환경에서 인증서가 필요할 때 사용하세요.
Docker에서 인증서를 안전하게 마운트하려면?
읽기 전용으로 마운트하고(:ro), Swarm 모드에서는 개인키에 Docker 시크릿을 사용하며, 리버스 프록시 컨테이너만 인증서 파일에 접근할 수 있도록 합니다. 인증서를 Docker 이미지에 절대 포함시키지 마세요 — 만료되면 이미지를 다시 빌드해야 합니다.
GetHTTPS 인증서를 Kubernetes에서 사용할 수 있나요?
네. GetHTTPS 인증서 파일로 Kubernetes TLS 시크릿을 생성합니다:
kubectl create secret tls my-tls-cert --cert=fullchain.pem --key=privkey.pem
그런 다음 Ingress 리소스에서 참조합니다. Kubernetes에서 자동 갱신을 위해서는 Let’s Encrypt ClusterIssuer가 포함된 cert-manager를 고려하세요.