Caddy を Docker のリバースプロキシに:自動 HTTPS 付き Compose 構成
Caddy を Docker のリバースプロキシとして動かす方法:1つの compose スタック、共有ネットワーク、サービス名への reverse_proxy と自動 HTTPS。caddy-docker-proxy のラベルが役立つ場面も解説。
結論:ポート 80 と 443 を公開する専用コンテナで Caddy を動かし、それとアプリのコンテナを1つの共有 Docker ネットワークに接続して、`reverse_proxy` を各サービス名と内部ポートに向けます(例:`reverse_proxy app:3000`)。Caddy は Docker の組み込み DNS で名前を解決し、すべてのサイトアドレスについて TLS 証明書を自動で取得します。
このガイドでは、まずシンプルな Caddyfile による方法を示し、次にラベル駆動の caddy-docker-proxy 版を紹介して、単一の VPS でそれぞれがどんな場合に適しているかを説明します。Docker ではなくホスト上で直接 Caddy を動かす場合は、基本のCaddy リバースプロキシガイドを参照してください。
構成要素の関係
外部にポートを公開するのは Caddy コンテナだけです。すべてのバックエンドコンテナはユーザー定義のブリッジネットワーク(ここでは `web`)に参加し、そのネットワーク上でのみポートを公開します。ユーザー定義ネットワークで使える Docker の組み込み DNS サーバーにより、Caddy はサービス名やコンテナ名で `app:3000` に到達できるので、再作成のたびに変わるコンテナ IP をハードコードする必要はありません。
バックエンドを公開しないことは見た目以上に重要です。Docker は独自の iptables ルールを書き込むため、`ports:` で公開したポートは、ufw がブロックしていると表示していてもインターネットから到達できることがあります。プロキシの背後に置くべきものには、`expose:` を使うか、ポートマッピングを一切しないでください。
| コンポーネント | 役割 | ホストで公開? |
|---|---|---|
| caddy コンテナ | TLS 終端、ホスト名によるルーティング、HTTP から HTTPS へのリダイレクト | はい:80/tcp、443/tcp、443/udp |
| web ネットワーク | サービス名用の DNS を内蔵した共有ブリッジネットワーク | いいえ |
| アプリコンテナ | あなたのサービス(Web アプリ、API、ダッシュボード) | いいえ、Caddy 経由でのみ到達可能 |
| caddy_data ボリューム | 証明書、秘密鍵、ACME アカウント | いいえ、ただし永続化が必須 |
手順:docker compose とシンプルな Caddyfile
- 01.Caddy を起動する前に、各ホスト名の A レコード(IPv6 を使う場合は AAAA レコードも)を VPS に向けておきます。そうしないと証明書の発行に失敗します。
- 02.共有ネットワークを一度だけ作成します:`docker network create web`。
- 03.下記のとおり `/opt/proxy/compose.yaml` と `/opt/proxy/Caddyfile` を作成し、`/opt/proxy` で `docker compose up -d` を実行します。
- 04.各アプリのスタックを外部ネットワーク `web` に接続し、`docker compose up -d` で起動します。
- 05.`/opt/proxy` で `docker compose logs -f caddy` を実行し、最初の証明書が発行される様子を確認します。
- 06.Caddyfile を編集したら、ダウンタイムなしでリロードします:`docker compose exec -w /etc/caddy caddy caddy reload`。
# /opt/proxy/compose.yaml
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data # certificates and ACME account keys
- caddy_config:/config
networks:
- web
volumes:
caddy_data:
caddy_config:
networks:
web:
external: trueアプリを接続して Caddyfile を書く
各アプリケーションのスタックで、同じ外部ネットワークを宣言します。サービス名が、Caddy が接続するホスト名になります。`caddy_data` ボリュームは絶対に失ってはいけません。削除すると Caddy はすべての証明書を再要求することになり、ACME のレート制限に達する可能性があります。
# /opt/app/compose.yaml
services:
app:
image: ghcr.io/example/app:latest
restart: unless-stopped
expose:
- "3000" # reachable on the network, NOT published on the host
networks:
- web
networks:
web:
external: true
# /opt/proxy/Caddyfile
{
email [email protected]
}
app.example.com {
reverse_proxy app:3000
}
grafana.example.com {
reverse_proxy grafana:3000
}
api.example.com {
reverse_proxy api-1:8080 api-2:8080 {
lb_policy round_robin
health_uri /healthz
health_interval 10s
}
}Caddy のバックエンド:上流、ロードバランシング、ヘルスチェック
Caddy の用語では、バックエンドは `reverse_proxy` の上流(upstream)です。1つのディレクティブに複数の上流を並べ、`round_robin`、`least_conn`、`ip_hash` などの `lb_policy` を選び、`health_uri` でアクティブヘルスチェックを追加すれば、障害の起きたコンテナはローテーションから外れます。アプリにヘルスエンドポイントがない場合は、パッシブチェック(`fail_duration`、`max_fails`)も便利です。
Caddy はデフォルトで、プロキシするリクエストに `X-Forwarded-For`、`X-Forwarded-Proto`、`X-Forwarded-Host` を付けます。アプリがプロキシのネットワーク範囲を信頼するよう設定すれば、実際のクライアント IP を把握し、`https://` のリンクを生成できます。WebSocket と gRPC は追加設定なしで `reverse_proxy` を通ります。平文の gRPC には `reverse_proxy h2c://service:50051` を使います。
Caddyfile の代わりにラベル:caddy-docker-proxy
「Caddy proxy manager」を探している人が求めているのは、たいてい Nginx Proxy Manager のような機能です。コンテナを追加するだけで、中央の設定ファイルを編集せずにホスト名と証明書が手に入る仕組みです。Caddy には公式の GUI はありませんが、コミュニティプロジェクトの caddy-docker-proxy(github.com/lucaslorentz/caddy-docker-proxy)がコンテナのラベルから Caddyfile を生成し、コンテナの起動・停止に合わせて Caddy をリロードします。
これには Docker ソケットへの読み取りアクセスが必要で、それは事実上ホストの root 権限に相当します。ソケットは読み取り専用でマウントし、プロキシのイメージを最新に保ち、少数のサービスしか動かさないならシンプルな Caddyfile を選びましょう。
services:
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- CADDY_INGRESS_NETWORKS=web
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- caddy_data:/data
networks:
- web
whoami:
image: traefik/whoami
networks:
- web
labels:
caddy: whoami.example.com
caddy.reverse_proxy: "{{upstreams 80}}"
volumes:
caddy_data:
networks:
web:
external: trueシンプルな Caddyfile と caddy-docker-proxy の比較
| シンプルな Caddyfile | caddy-docker-proxy のラベル | |
|---|---|---|
| 設定の置き場所 | git で管理する1つのファイル | 複数の compose ファイルに散らばるラベル |
| Docker ソケットへのアクセス | 不要 | 必要(セキュリティ上注意) |
| 新しいサービスの追加 | Caddyfile を編集し `caddy reload` を実行 | ラベルを追加してコンテナを起動 |
| イメージ | 公式の `caddy:2` | Caddy ベースのコミュニティイメージ |
| デバッグ | ファイルを直接読む | ログで生成された設定を確認 |
| 最適な用途 | 小規模で安定した VPS 構成 | 短命または頻繁に変わる多数のサービス |
VPS で運用する
NoctisNode の KVM VPS ではフル root 権限と専用 IPv4 が得られるので、1つの Caddy コンテナから 80 と 443 を公開し、それ以外をすべて隠す構成は簡単です。Caddy と軽量なコンテナ数個なら Wisp(1 vCPU、1 GB RAM)で十分で、重めのスタックには Pulse や Umbra が向いています。証明書発行の詳細と失敗パターンはCaddy の自動 HTTPS の仕組みで、生の TCP や SSH の転送はCaddy による TCP と SSH のプロキシで解説しています。ホストの SSH はSSH 強化ガイドで固めましょう。
よくある質問
QCaddy から Docker コンテナへリバースプロキシするには?
ACaddy とコンテナを同じユーザー定義 Docker ネットワークに置き、Caddyfile で `reverse_proxy service-name:port` を使います。Caddy コンテナ内では `localhost` は Caddy 自身を指すため、使わないでください。
QCaddy がコンテナに対して 502 Bad Gateway を返すのはなぜですか?
A多くの場合、コンテナが共有ネットワーク上にない、ポートが間違っている(ホストで公開したポートではなくコンテナの内部ポートを使う)、またはアプリがコンテナ内で 127.0.0.1 だけを待ち受けている、のいずれかです。アプリを 0.0.0.0 にバインドし、`docker network inspect web` を確認してください。
Qcaddy-docker-proxy は安全に使えますか?
A問題なく動作しますが、ホストに対する root 相当の制御権を与える Docker ソケットが必要です。読み取り専用でマウントし、イメージを最新に保ち、Caddy の管理エンドポイントを公開しないようにしてください。
QDocker で Caddy は証明書をどこに保存しますか?
A公式イメージでは `/data` に保存されるので、そこに名前付きボリュームをマウントします。これを失うと、すべての証明書を再発行することになり、ACME のレート制限に達する可能性があります。
QWeb UI 付きの Caddy proxy manager はありますか?
ACaddy 自体には公式の Web UI はなく、Caddyfile、JSON、または管理 API で設定します。caddy-docker-proxy のようなラベルベースのツールを使えば、コンテナを追加するだけですぐ使える似たワークフローが得られます。