Docker環境で動くClashを単なるHTTPプロキシとして使うだけなら、アプリケーションごとにHTTP_PROXYやHTTPS_PROXYを設定すれば十分です。しかし開発用サーバー、CIランナー、複数のコンテナで構成されたサービスでは、すべてのプロセスがプロキシ環境変数に対応しているとは限りません。パッケージマネージャー、Git、DNSクライアント、サブプロセスが個別に通信するため、設定漏れがあると一部のリクエストだけが直接接続されます。この記事では、Mihomoを中心にDocker上のClashを透過プロキシとして動作させるためのYAML設定を組み立て、コンテナ通信の捕捉、DNSリークの抑制、ルール不一致の確認、パッケージ取得タイムアウトの切り分けまで実践的に解説します。
Docker透過プロキシの基本設計
透過プロキシでは、アプリケーションがプロキシの存在を意識する必要がありません。コンテナが外部へTCP接続を開始すると、Dockerホストのiptablesまたはnftablesルールが宛先をClashの透過ポートへリダイレクトします。Clashは接続元と宛先を読み取り、通常のルールエンジンでプロキシ、直接接続、拒否のいずれかを選択します。
Dockerで最初に決めるべきなのは、Clashコンテナのネットワーク方式です。一般的には次の2方式があります。
- hostネットワーク:Clashがホストのネットワーク名前空間を直接利用します。iptables連携が分かりやすく、LinuxサーバーやCI専用ホストで扱いやすい方式です。
- bridgeネットワーク:Clashを独立したコンテナとして動かします。構成を分離できますが、ホストとコンテナ間の転送、ポート公開、戻りパケットの経路を追加で設計する必要があります。
透過プロキシのデバッグを優先するなら、最初はnetwork_mode: hostを推奨します。Docker DesktopのmacOSやWindowsではLinuxのホストネットワークと挙動が異なるため、以下の例は主にLinuxホストを対象にしています。特にCI環境では、実行ランナーの権限、Docker-in-Dockerの有無、ホスト側ファイアウォールの管理方法を事前に確認してください。
Mihomo向けYAMLの中核設定
透過プロキシ用の設定では、通常のHTTP混合ポートに加えてredir-portまたはtproxy-portを有効にします。TCPだけを扱うならREDIRECT方式が比較的簡単です。UDPやDNSを含む幅広い通信を捕捉する場合は、Linuxカーネルとファイアウォールが対応していることを確認したうえでTProxyを選択します。
mixed-port: 7890 redir-port: 7892 # TCPのREDIRECT用 tproxy-port: 7893 # TCP/UDPのTProxy用 allow-lan: true bind-address: '*' mode: rule log-level: info ipv6: false external-controller: '127.0.0.1:9090' secret: 'change-this-secret' profile: store-selected: true store-fake-ip: true
allow-lanとbind-addressは、別コンテナやLAN上のクライアントからClashへ接続させる場合に必要です。ただし、外部コントローラーまで0.0.0.0で公開する必要はありません。管理APIは127.0.0.1:9090に限定し、Dockerホストから操作する構成のほうが安全です。
DNSは透過プロキシの安定性を左右します。ドメインベースのルールを確実に適用したい場合は、MihomoのDNSリスナーをコンテナの外部から利用できるようにし、fake-ipを基本にします。Dockerの内蔵DNSである127.0.0.11を無条件に上流へ転送すると、名前解決だけがホスト側へ流れてDNSリークの原因になることがあります。
dns: enable: true listen: '0.0.0.0:1053' enhanced-mode: fake-ip fake-ip-range: '198.18.0.1/16' nameserver: - 'https://1.1.1.1/dns-query' - 'https://dns.google/dns-query' fallback: - 'tls://1.1.1.1:853' fake-ip-filter: - '*.lan' - '*.local' - 'localhost.ptlogin2.qq.com' - '+.stun.*.*' sniffer: enable: true override-destination: true sniff: HTTP: ports: [80, 8080] TLS: ports: [443, 8443]
fake-ip-filterには、ローカルサービスやSTUNのように実IPを必要とする通信だけを追加します。除外範囲を広げすぎると、ドメインベースのルーティングやDNSリーク対策が弱くなります。社内ドメインは+.corp.exampleのように明示的に登録し、検証後に対象を増やすのが安全です。
Dockerネットワークとファイアウォール連携
YAMLを用意しただけでは、コンテナの通信はClashへ届きません。Dockerホスト上で、対象の送信元ネットワークから出るTCP通信をClashの透過ポートへ転送します。まずはREDIRECT方式でHTTPとHTTPSを確認し、正常に動作してからTProxyやUDPへ拡張すると、原因を切り分けやすくなります。
services: clash: image: metacubex/mihomo:latest container_name: clash network_mode: host cap_add: - NET_ADMIN - NET_RAW volumes: - ./config:/root/.config/mihomo restart: unless-stopped
NET_ADMINはルーティングや透過処理に必要になる場合がありますが、コンテナへ過剰な権限を与えることにもつながります。実際に必要な権限はイメージと実行方式で異なるため、検証環境で確認し、本番では不要な権限を削減してください。CIでDockerソケットを共有している場合は、Clashの設定変更権限が他のジョブへ波及しないよう、専用ランナーを使うことをおすすめします。
iptablesを使う場合の概念的なルールは次のようになります。ここで172.18.0.0/16は例示的なDockerネットワークであり、実際の値はdocker network inspectで確認してください。
# 転送対象ネットワークの例
iptables -t nat -N CLASH
iptables -t nat -A CLASH -d 127.0.0.0/8 -j RETURN
iptables -t nat -A CLASH -d 10.0.0.0/8 -j RETURN
iptables -t nat -A CLASH -d 172.16.0.0/12 -j RETURN
iptables -t nat -A CLASH -d 192.168.0.0/16 -j RETURN
iptables -t nat -A CLASH -p tcp -j REDIRECT --to-ports 7892
iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner 1000 -j RETURN
iptables -t nat -A PREROUTING -s 172.18.0.0/16 -p tcp -j CLASH
上の例ではプライベートアドレスを除外しています。これにより、Docker内部サービス、データベース、サービスディスカバリへの接続が意図せずプロキシへ送られることを防ぎます。一方、社内ネットワーク上の特定ドメインだけをプロキシしたい場合は、アドレス帯を一括除外するのではなく、ルールと経路を分けて設計してください。ホスト自身の通信を対象にする場合は、ClashプロセスのUID除外がないとリダイレクトループになりやすい点にも注意が必要です。
ルール設計とコンテナ除外
透過プロキシでは、アプリケーションが送信する接続先を正確に把握しづらいため、ルールの順序が重要です。ローカルネットワーク、Docker内部ネットワーク、プロキシ対象、その他の直接接続という順に整理すると、意図しない経路を減らせます。GEOIP,CN,DIRECTのような広いルールを上位に置くと、プロキシしたいCDNやAPIまで直接接続になる可能性があります。
proxy-groups: - name: PROXY type: select proxies: - Auto - DIRECT - name: Auto type: url-test use: - nodes url: 'https://www.gstatic.com/generate_204' interval: 300 rules: - DOMAIN-SUFFIX,localhost,DIRECT - DOMAIN-SUFFIX,local,DIRECT - IP-CIDR,127.0.0.0/8,DIRECT,no-resolve - IP-CIDR,10.0.0.0/8,DIRECT,no-resolve - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve - RULE-SET,proxy-sites,PROXY - MATCH,DIRECT
no-resolveは、IP-CIDRルールに到達した際に不要なDNS逆引きを避けるための指定です。ルールプロバイダーを利用する場合は、コンテナ内の保存先が書き込み可能であること、起動直後に外部URLへ到達できることを確認します。CIでは毎回空のボリュームから起動するとルール取得がビルド開始を遅らせるため、プロバイダーのキャッシュを永続化するか、ジョブ開始前に明示的な更新ステップを置くと安定します。
DNSリークとタイムアウトの診断
設定が正しいかは、Clashのログだけでなく、コンテナ内部、ホストの接続テーブル、ダッシュボードの接続一覧を組み合わせて確認します。まず対象コンテナで名前解決とHTTPS接続を個別にテストしてください。
# コンテナのDNS設定を確認 docker exec app cat /etc/resolv.conf # 名前解決と接続を分離して確認 docker exec app getent hosts example.com docker exec app curl -v --connect-timeout 10 https://example.com/ # Clashコンテナのログを確認 docker logs -f clash # ホストで透過ポートの待受を確認 ss -lntup | grep -E '7892|7893|1053' # ルールカウンターとNAT経路を確認 iptables -t nat -vnL CLASH
DNSリークを調べるときは、/etc/resolv.confに記載されたサーバーだけで判断してはいけません。アプリケーションが独自のDoH、DoT、DNS over QUICを使う場合、53番ポートのリダイレクトだけでは捕捉できません。Clashの接続一覧でDNS関連の外向き接続を確認し、必要に応じて既知の外部DNS宛てUDP/TCP 53をClashへ転送します。DoHを使うアプリは、アプリ側で無効化するほうがルール管理しやすい場合もあります。
パッケージ取得がタイムアウトする場合は、次の順番で切り分けます。第一に、Clashの外部プロキシノード自体がテストURLへ接続できるかを確認します。第二に、パッケージリポジトリのドメインがルールプロバイダーに含まれているかを調べます。第三に、リポジトリがIPv6を返しているのにClash側でIPv6を無効化していないか、あるいは逆にIPv6経路だけが不完全ではないかを確認します。最後に、TLS検証エラー、MTU不足、企業ネットワークの証明書差し替えをログから確認します。
本番運用前の確認事項
開発環境で通信が通った後も、CIや共有サーバーでは可用性と安全性を確認する必要があります。Clashの管理APIをインターネットへ公開せず、設定ファイルにプロキシ認証情報を平文で保存する場合は、ファイル権限を限定してください。ノード設定を環境変数や秘密情報管理サービスから生成する方法も有効です。
- DockerネットワークのCIDRと、除外対象のプライベートアドレスを記録する。
- Clash自身、DNS、ルールプロバイダー更新の通信がリダイレクトループにならないことを確認する。
- HTTP、HTTPS、DNS、Git、パッケージマネージャーを別々にテストする。
- ノード障害時に
fallbackまたはurl-testが期待どおり切り替わるか確認する。 - コンテナ再起動後もiptablesルール、fake-ip状態、ルールプロバイダーが復元されるようにする。
- ログを無期限に保存せず、CIの秘密情報やAuthorizationヘッダーが出力されない設定にする。
透過プロキシは、環境変数を配布するだけの方法よりも導入作業が増えます。しかし、プロキシ非対応のツールを含むビルドチェーン全体を一貫して制御でき、コンテナごとの設定漏れも減らせます。まずはREDIRECTとTCP通信で小さく検証し、DNS、UDP、TProxyを順番に追加するのが最も安定した進め方です。Docker上のClashを継続的に利用するなら、YAML、ファイアウォールルール、診断コマンドを同じリポジトリで管理し、環境ごとの差分を明示しておくと、障害対応とCIの再現性が大きく向上します。
Clash でトラフィックを完全制御
Windows・macOS・Linux・Android・iOS 対応。柔軟なルール、すぐに使えます。