DockerでCaddyをリバースプロキシとして設定:SSLと煩雑な設定に別れを告げよう

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Nginxの設定とCertbotという悪夢

Nginxは今でも金字塔的な存在ですが、正直なところ、何百行にも及ぶ設定ファイルの管理は苦行です。以前、Dockerで動かしているサブドメインのSSLをCertbotで更新しようとして、デバッグに3時間も費やしたことがあります。問題の多くは、Nginxによる本番環境へのデプロイでよく遭遇するリダイレクトルールの重複や.well-known設定のミスにありました。

5〜10個のマイクロサービスを管理している場合、proxy_passheaderssl_certificateの設定を何度も繰り返すのは時間の無駄です。そこで救世主として現れたのがCaddyです。Caddyはすべてを手動で行うのではなく、デフォルトですべてを自動化すること、特にAutomatic HTTPSに重点を置いています。追加のプラグインなしで、Let’s EncryptからのSSL証明書の発行と更新をすべて自動で行ってくれます。

なぜ Docker環境でCaddyがNginxを凌駕するのか?

  • 超シンプルな設定: 完全なHTTPS対応サイトが、わずか3行のコードで構築できます。
  • デフォルトでHTTP/3対応: Caddyは標準でHTTP/3 (QUIC) をサポートしており、従来のHTTP/2よりもページの読み込み速度が約20〜30%向上します。
  • SSLの100%自動化: Certbotのことは忘れてください。Caddyが証明書の取得から、期限が近づいた際の更新まで、AからZまですべて処理します。
  • Go言語製: システムライブラリに依存せず、コンテナ内で単一のバイナリとして非常に軽量に動作します。

ステップ1:共通のDockerネットワークを作成する

Caddyを「ゲートキーパー」として機能させるには、他のコンテナと通信するための専用の経路が必要です。ここではcaddy_netという名前のネットワークを作成します。これによりシステムのセキュリティが向上します。内部のアプリはポートを外部に公開する必要がなくなり、ポート80と443からのトラフィックを受け取れるのはCaddyだけになります。

docker network create caddy_net

実際、この方法を導入したことで、データベースやRedisなどのサービスがネットワーク内に隠蔽され、ポートスキャンのリスクを最大90%削減することができました。Docker Bench for Securityなどのツールで診断すると、その効果がより明確になります。

ステップ2:Docker ComposeでCaddyをデプロイする

まず、すべての設定を保存するためのcaddyディレクトリを作成します。将来的にサービスが増える場合は、Docker Composeの最適化テクニックを利用してYAMLファイルを整理するのが効率的です。標準的なディレクトリ構造は以下のようになります:

caddy/
├── docker-compose.yml
└── Caddyfile

docker-compose.ymlの内容は以下の通りです:

version: "3.9"
services:
  caddy:
    image: caddy:2.7-alpine
    container_name: caddy_proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp" # HTTP/3を有効にするために重要
    networks:
      - caddy_net
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

networks:
  caddy_net:
    external: true

volumes:
  caddy_data:
  caddy_config:

警告: /dataのボリュームマウントを絶対に忘れないでください。ここにはSSL証明書が保存されます。このデータを紛失した状態でコンテナの再起動を繰り返すと、証明書の発行要求が多すぎるとしてLet’s Encryptから7日間のIPブロック(Rate Limit)を受ける可能性があります。

ステップ3:Caddyfileの設定 — シンプルさの力

例えば、Django REST FrameworkのDocker化などで作成したアプリコンテナが、ポート3000で動いているとします。Nginxなら20行ほど必要ですが, Caddyならこれだけです:

app.yourdomain.com {
    reverse_proxy my_awesome_app:3000
}

信じられますか?これだけです。CaddyはSSLが必要であることを自動的に理解し、証明書を申請し、プロキシを設定します。さらにセキュリティを強化し、Google PageSpeedを最適化するためにデータを圧縮したい場合は、以下の構造を使用します:

api.yourdomain.com {
    encode gzip zstd
    header {
        Strict-Transport-Security "max-age=31536000;"
        X-Content-Type-Options nosniff
    }
    reverse_proxy api_container:8080
}

ステップ4:起動とログの確認

以下のコマンドを実行してシステムを起動します:

docker-compose up -d

Caddyが正しく動作しているか確認するには、ログをチェックします。ctop: ターミナルでDockerコンテナをリアルタイム監視ツールを使用すれば、ログとリソース消費量を同時に確認できるので便利です。SSL証明書が発行されたかどうかを確認する方法は以下の通りです:

docker logs -f caddy_proxy

ログにcertificate obtained successfullyと表示されれば、ブラウザを開いて、苦労することなく緑色の鍵マークを確認できるはずです。

実践的なヒント:Caddyでエラーが発生した時は?

実際の運用で最も多いエラーは502 Bad Gatewayです。このエラーが発生した場合は、アプリケーションコンテナがcaddy_netに参加しているか確認してください。Caddyは見えないものに接続することはできません。

Cloudflareを使用している方への注意点:SSL/TLS設定を「Full (strict)」にしてください。「Flexible」に設定していると、CloudflareがサーバーにHTTPでリクエストを送るのに対し、CaddyがHTTPSを強制しようとするため、リダイレクトループ(Too many redirects)に陥ります。

Caddyは単なるツールではなく、よりスマートに働くための手段です。煩雑な設定に時間を費やす代わりに、インフラ部分はCaddyに任せ、皆さんはコードを書くことや製品の最適化に集中しましょう。

Share: