Dockerコンテナは通常、SSL自体を処理しません。前段に配置されたリバースプロキシがTLSを終端し、復号されたトラフィックをコンテナに転送します。このガイドでは、最も一般的な3つのアプローチを解説します。
アプローチ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
# docker-compose.yml(デプロイモード)
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を検討してください。