DockerでCloudflare Tunnelを構築:ポート開放なしでHomeLabサービスを安全に外部公開する方法

自宅サーバーを立ち上げるのは簡単ですが、安全に外部へ公開するのは容易ではありません。多くのセルフホスト愛好家は、内部のダッシュボード、API、モニタリングツールを外部からアクセスできるようにしようとした瞬間に壁にぶつかります。プロバイダ(ISP)がCGNAT(Carrier-Grade NAT)を採用していたり、数日おきにIPアドレスが変わったりする場合、従来のDynamic DNSやルーターのポート転送はすぐに管理上の大きな悩みの種となります。

Cloudflare Tunnelは、この煩雑な問題をすべて解決してくれます。ルーターのファイアウォールで80番や443番のポートを開放する代わりに、軽量なcloudflaredデーモンがCloudflareのエッジデータセンターに向けてアウトバウンド(外向き)接続のみを確立します。Cloudflareがエッジ側でTLSを終端してDDoS攻撃の急増を吸収し、リクエストを安全にDockerコンテナへプロキシします。

ルーターのポート開放は一切不要で、グローバルIPが露出することもありません。オリジンサーバーはShodan、Censys、家庭用IP帯をスキャンする自動ポートスキャナーから完全に不可視のまま保たれます。

クイックスタート(5分で完了)

テスト用コンテナとともに、わずか5分で実際に動作するトンネルをデプロイする手順は以下の通りです。

事前準備

  • Cloudflareのネームサーバーに向けられた独自ドメイン(無料プランで問題ありません)。
  • LinuxサーバーまたはRaspberry PiにインストールされたDocker Engine 24以上およびDocker Compose v2。
  • Cloudflare Zero Trustアカウント(最大50ユーザーまで無料)。

ステップ1: トンネルトークンの生成

Cloudflare Zero Trustダッシュボードを開き、Networks > Tunnelsに移動します。Create a tunnelをクリックし、コネクタの種類としてCloudflaredを選択し、名前(例: homelab-tunnel)を付けて次に進みます。

Cloudflareが各環境向けのインストールコマンドを表示します。Dockerタブを開き、コマンド文字列の中から--tokenの直後にある長いBase64ハッシュをコピーします。

ステップ2: Docker Composeによるデプロイ

ホスト上に新しいプロジェクトディレクトリ(例: ~/homelab/cloudflare-tunnel)を作成し、compose.yamlファイルを追加します。ルーティングが正しく機能しているかをすぐに検証できるよう、コネクタデーモンとTraefikの軽量なwhoamiコンテナの両方を定義します。

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
    environment:
      - TUNNEL_TOKEN=eyJhIjoiZXhhbXBsZXRva2VuMTIzNDU2Nzg5MGFiY2RlZiIsInQiOiI...
    networks:
      - homelab_net

  whoami-test:
    image: traefik/whoami:latest
    container_name: whoami-test
    restart: unless-stopped
    networks:
      - homelab_net

networks:
  homelab_net:
    name: homelab_net
    driver: bridge

デタッチモードでスタックを起動します。

docker compose up -d
docker compose logs -f cloudflared

2〜3秒以内にログ上にRegistered tunnel connection connIndex=0 ...のような行が表示され、複数のエッジ拠点とのアクティブな接続が確立されたことが確認できるはずです。

ステップ3: パブリックホスト名をコンテナへルーティング

Cloudflare Zero Trustダッシュボードに戻ります。

  1. トンネル設定内のPublic Hostnameタブを開き、Add a public hostnameをクリックします。
  2. 設定したいサブドメイン(例: whoami.yourdomain.com)を入力します。
  3. アップストリームサービスを設定します:
    • Type: HTTP
    • URL: whoami-test:80(共有ブリッジネットワーク上では、Dockerの組み込みDNSが自動的にコンテナ名を解決します)。
  4. ホスト名を保存します。

ブラウザでhttps://whoami.yourdomain.comにアクセスします。Cloudflareがエッジ証明書で即座にSSLを終端し、自宅ルーターのポートを1つも開放することなく、コンテナのヘッダー情報が表示されます。

詳細解説

トンネルの動作原理を理解しておくと、エッジルーティングの問題や接続切断のトラブルシューティングが格段に容易になります。

Cloudflare TunnelがCGNATを回避する仕組み

大半の家庭用光回線やモバイル回線プロバイダは、CGNAT(Carrier-Grade NAT)を使用してIPv4アドレスブロックを節約しています。CGNAT構成では、数千台の加入者ルーターが上流の共有グローバルIPプールを共有します。そのため、ルーター宛てのインバウンド(内向き)接続は届かず、プロバイダ側のキャリアファイアウォールが機器に到達する前に要求されていないパケットを破棄してしまいます。

Cloudflare Tunnelはこの構造を根本から逆転させます。受信リクエストを待ち受けるのではなく、cloudflaredバイナリがUDP 7844番ポート経由で4本のアウトバウンド(外向き)永続QUICトンネルを自発的に確立します(UDPトラフィックが制限されている場合は標準のTCP 443番ポートにフォールバック)。ステートフルNATファイアウォールは確立済みのアウトバウンド接続を許可するため、動的IPの更新やホールパンチングを行わなくても、双方向トラフィックがトンネルを経由して流れるようになります。

アーキテクチャ概要

[ インターネットクライアント ]
        |
        v (HTTPS: 443)
[ Cloudflare エッジ / WAF / Anycast DNS ]
        |
        v (UDP 7844経由のアウトバウンドQUICトンネル)
[ ステートフルファイアウォール / ルーター (インバウンド開放ポート: 0) ]
        |
        v
[ cloudflaredコンテナ ]
        |
        v (内部Dockerネットワーク: bridge)
[ アプリケーションコンテナ (例: whoami-test:80) ]

リモート管理トンネル vs ローカル管理トンネル

Cloudflareは2つの管理ワークフローをサポートしています。

  • ダッシュボード管理(リモート): Zero Trust Webコンソールを通じて管理され、環境変数トークンに紐付けられます。サーバーのターミナルを操作することなく、新しいサブドメインの紐付け、バックエンドターゲットの切り替え、アクセスルールの適用を数秒で行えます。
  • ローカル管理(CLI / GitOps): コンテナにマウントされたローカルのconfig.ymlと認証情報ファイルによって制御されます。この方式は、ルーティングの変更をコードレビューやCI/CDパイプラインに通す必要があるGitOpsワークフローに適しています。

ほとんどの個人開発者や社内開発環境では、ダッシュボード管理トンネルを採用することで、定型的な設定同期の手間を省き、YAMLの構文ミスによるダウンタイムを防ぐことができます。

高度な活用法

HTTPトラフィックの疎通が確認できたら、エンドポイントのセキュリティを強化し、複数コンテナ環境をすっきりと公開できるように設定しましょう。

1. Cloudflare Accessによる内部アプリケーションの保護

PortainerやGrafanaのような認証なしの管理ダッシュボードを直接Webに公開すると、数時間のうちに自動化された総当たり攻撃(ブルートフォース攻撃)の標的になります。Cloudflare Accessはエッジネットワーク上にID認証ゲートを配置し、サーバーにトラフィックが届く前にアクセスを精査します。

Zero Trustダッシュボードでの設定手順は以下の通りです。

  1. Access > Applications > Add an applicationに移動します。
  2. Self-hostedを選択し、サービス名を入力してドメイン(例: vaultwarden.yourdomain.com)を指定します。
  3. アクセスポリシーを追加します: 自身のメールアドレス(例: [email protected])または会社のメールドメインに一致するIncludeルールを選択します。
  4. 任意のIDプロバイダを選択します: メールワンタイムパスワード(OTP)、GitHub OAuth、Google、またはAuthentikなどのOIDCエンドポイント。

URLにアクセスしようとする訪問者は、必ず事前の認証が求められます。不正なリクエストはCloudflareのエッジサーバーから直接403レスポンスを受け取るため、自宅ホストが悪意あるスキャンから完全に隔離されます。

2. 既存のDockerスタックとの連携

すべてのアプリケーションを1つの巨大なcomposeファイルにまとめる必要はありません。代わりに、共有の外部Dockerブリッジネットワークを作成します。

docker network create homelab_proxy

独立したcompose.yaml内で、トンネルコネクタをそのネットワークにバインドします。

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
    networks:
      - homelab_proxy

networks:
  homelab_proxy:
    external: true

これで、サーバー上の個別のアプリケーションスタックがhomelab_proxyに参加し、コンテナ名でルーティング可能になります。

services:
  my-api:
    image: my-internal-api:v1.2
    container_name: my-api
    networks:
      - homelab_proxy
      - default

networks:
  homelab_proxy:
    external: true

Cloudflareダッシュボードで、api.yourdomain.comを直接http://my-api:8080へルーティングします。

3. 内部の自己署名TLSへの対応

独自の自己署名HTTPS証明書を強制するコンテナ(Proxmox VEや初期設定状態のNginxプロキシなど)にトラフィックをルーティングすると、cloudflaredはTLS検証エラーを出してアップストリームとのハンドシェイクを中断します。Zero Trustダッシュボードのパブリックホスト名設定からこの検証をバイパスできます。Additional application settings > TLSを展開し、No TLS Verifyを有効(トグルをオン)にしてください。

運用のポイントと実践的なTips

トンネル経由で重要なサービスを運用する前に、把握しておくべき運用上の注意点がいくつかあります。

  • 利用規約(セクション2.8)に注意: Cloudflareの非Enterpriseプランの利用規約では、大容量の非HTMLコンテンツのプロキシ配信が制限されています。高帯域幅の動画ストリーミング(40Mbps以上の4K PlexやJellyfinの生再生など)を常時流したり、日次500GBのバックアップダンプをトンネル経由で転送したりすると、アカウントが自動停止フラグの対象になる可能性があります。トンネルはWebアプリケーション、API、SSH/VNCの踏み台サーバーなどの用途にとどめましょう。
  • アウトバウンドUDP 7844番ポートがブロックされていないことを確認: デフォルトでは、cloudflaredはQUIC経由のHTTP/3を優先します。QUICはTCPに比べてプロバイダ起因のジッターや一時的なパケットロスをはるかにスムーズに処理し、自宅WAN回線が瞬断した場合でも100ミリ秒未満で再接続します。ルーターのファイアウォールで送信UDP 7844番ポートが遮断されていないか確認してください。
  • 実際のクライアントIPを取得する: Cloudflareがユーザーとオリジンサーバーの間に介在するため、アプリケーションの生TCPソケットにはCloudflareのプロキシIPが見えます。訪問者のアクセスログ記録やレートリミットを適切に行うには、リバースプロキシ(またはアプリケーションのミドルウェア)側でCF-Connecting-IPリクエストヘッダーを読み取るように設定してください。
  • コンテナのヘルスチェックを追加する: コネクタが停止した場合、Cloudflareエッジノードはトラフィックを迂回させますが、Dockerは明示的なチェックがない限り停止状態のコネクタを自動再起動しません。composeファイルに以下のブロックを追加してください:
    healthcheck:
      test: ["CMD", "cloudflared", "tunnel", "--version"]
      interval: 30s
      timeout: 5s
      retries: 3

Cloudflare TunnelとDockerを組み合わせることで、自宅サーバー運用の最大の弱点(煩雑なルーター設定、CGNAT回避の苦労、グローバルIPの露出)を完全に解消できます。わずか数行のYAMLを書くだけで、強固なZero Trustエッジゲートウェイが数分で手に入ります。

Share: