Docker開発環境の通信をClashで透過プロキシ化する実践設定

Dockerで開発していると、イメージの取得、パッケージマネージャー、GitHub API、外部のビルドツールなど、コンテナからさまざまなネットワークへ接続する必要があります。各コンテナにHTTP_PROXYHTTPS_PROXYを設定する方法は分かりやすい一方、コンテナごとに環境変数を管理しなければならず、設定漏れやプロキシ経由の不要な通信が起きやすくなります。

そこで本記事では、Dockerホスト上で動作するClashまたはMihomoを通信の入り口として使い、Dockerブリッジネットワークから出る通信を透過的にプロキシ化する方法を解説します。コンテナ内にプロキシ設定を追加せず、Docker HubやGitHubなど必要な宛先だけをClashのルールで振り分ける構成です。Linuxホストを中心に、Docker Desktopでの制約、DNS障害、権限不足、通信ループの確認方法まで順番に整理します。

DockerとClashを接続する基本構成

通常のDockerコンテナは、docker0またはユーザー定義ブリッジを経由してホストのネットワークへ出ていきます。コンテナから見た宛先は外部サイトですが、ホスト側ではNATされた通信として扱われます。この経路上でClashの透過プロキシポートへリダイレクトすれば、コンテナ内のアプリケーションはプロキシの存在を意識せずに通信できます。

Clashの動作モードは役割によって異なります。一般的なHTTPプロキシポートは、アプリケーションが明示的にプロキシを指定する場合に使います。一方、Dockerの通信を強制的に捕捉するには、Linuxではredir-hostまたはredir-port、より広範囲の通信にはTUNモードを利用します。Dockerブリッジだけを対象にしたい場合、まずはredir方式から始めると経路を限定しやすく、トラブルの切り分けも簡単です。

方式コンテナ側の変更向いている用途注意点
HTTP_PROXY環境変数が必要対応アプリだけを個別にプロキシ化設定漏れ、HTTPSや非対応アプリの扱い
redir透過プロキシ原則不要DockerブリッジのTCP通信を一括処理iptables、DNS、ループ除外の設計が必要
TUN原則不要TCP・UDPを含む広範囲の通信権限、ルーティング、ホスト全体への影響

この構成では、Clash自身が外部プロキシへ接続するための通信まで再びリダイレクトしないことが重要です。Clashのプロセスを専用ユーザーで実行し、そのユーザーの通信をiptablesの除外対象にする設計が基本になります。

Mihomoの透過プロキシポートを設定する

LinuxホストでMihomoを使う場合は、まずローカルから到達できる透過プロキシポートを有効にします。以下は開発用ホストで使いやすい最小構成の例です。ポート番号は既存のサービスと重ならないように変更してください。

Mihomo 透過プロキシ設定
mixed-port: 7890
redir-port: 7892
tproxy-port: 7893
allow-lan: false
mode: rule

dns:
  enable: true
  listen: 127.0.0.1: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

redir-portはTCP通信をリダイレクトするためのポートです。UDPも透過処理したい場合は、Linuxのポリシールーティングとtproxy-portを組み合わせます。ただし、Dockerのイメージ取得、Git、一般的なパッケージダウンロードを安定させる目的なら、まずTCPのみで構成し、必要性を確認してからUDPへ拡張する方が安全です。

DNSは特に注意が必要です。コンテナのDNS問い合わせがホストやルーターへ直接送られると、ドメイン解決だけがプロキシ外へ漏れる場合があります。MihomoのDNSを有効にする場合でも、Dockerの内部DNSである127.0.0.11との関係を確認してください。コンテナ内の/etc/resolv.confを無理に書き換えると、サービスディスカバリやユーザー定義ネットワークの名前解決が壊れることがあります。

最初はTCPだけで検証しましょう:Docker Hub、GitHub、PyPI、npmなど開発で頻繁に使うサービスの接続を確認し、必要になった場合だけUDPやTUNを追加します。設定範囲を小さくすると、DNS障害とルーティング障害を分けて診断できます。

Dockerブリッジの通信をリダイレクトする

次に、Dockerブリッジから外へ出るTCP通信をClashのredir-portへ送ります。ここでは標準的なdocker0を例にしますが、Composeや複数プロジェクトで独自ネットワークを使っている場合は、対象インターフェースとサブネットを必ず確認してください。

iptablesによるDocker TCPリダイレクト
# 例: mihomoを実行する専用ユーザーを作成
sudo useradd --system --no-create-home mihomo

# Dockerから外部へ出るTCP通信をClashへ転送
sudo iptables -t nat -N CLASH_DOCKER
sudo iptables -t nat -A PREROUTING -i docker0 -p tcp -j CLASH_DOCKER

# ローカル宛て、プライベートネットワーク、Clashユーザーを除外
sudo iptables -t nat -A CLASH_DOCKER -d 127.0.0.0/8 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 10.0.0.0/8 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 172.16.0.0/12 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 192.168.0.0/16 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -m owner --uid-owner mihomo -j RETURN

# 上記に該当しないTCP接続をredir-portへ
sudo iptables -t nat -A CLASH_DOCKER -p tcp -j REDIRECT --to-ports 7892

ここで重要なのは、Dockerの内部通信までプロキシへ送らないことです。ユーザー定義ブリッジ上のコンテナ同士の接続、ホストのプライベートアドレス、Docker DNS、データベースや開発サーバーのローカルポートは、通常は直接通信させます。すべての宛先を無条件にリダイレクトすると、コンテナ間通信が遅くなったり、名前解決が失敗したりします。

iptablesのチェーン名や既存のDockerルールは環境によって異なります。nftablesを利用するディストリビューションでは、iptables互換レイヤーの挙動が異なる場合があるため、追加後に必ずiptables-savenft list ruleset、または各ディストリビューションのファイアウォール管理コマンドで確認してください。ルールを永続化しないままホストを再起動すると、通信が直接接続へ戻ることもあります。

対象ネットワークを限定する

本番用コンテナと開発用コンテナで通信ポリシーを分けたい場合は、すべてのDocker通信を対象にするのではなく、専用ブリッジを作成します。例えば、社内Gitやパッケージ取得が必要なサービスだけをdev-proxy-netへ接続し、そのインターフェースにだけルールを適用します。これにより、データベースや監視コンテナを意図せず外部プロキシへ送るリスクを下げられます。

開発用ネットワークの作成例
docker network create \
  --driver bridge \
  --subnet 172.30.0.0/16 \
  dev-proxy-net

docker run --rm -it \
  --network dev-proxy-net \
  python:3.12-slim \
  python -c "import urllib.request; print(urllib.request.urlopen('https://pypi.org').status)"

Docker DaemonとComposeの違いを理解する

「コンテナの通信」と「Dockerイメージの取得」は、同じように見えて経路が異なります。docker pullはDocker CLIではなくDocker Daemonが実行するため、Daemonがホスト上で直接外部へ接続している構成では、コンテナ向けのiptablesルールだけでは十分でないことがあります。

Docker Daemon自体にもプロキシを指定したい場合は、systemdのドロップイン設定を使います。ただし、この方法はDaemonが行う通信を一括してプロキシへ送るため、ClashのHTTPプロキシポートを利用できる環境で選択してください。透過プロキシと明示プロキシを同時に適用すると二重処理になる可能性があります。

Docker Daemonの明示プロキシ設定
sudo mkdir -p /etc/systemd/system/docker.service.d

sudo tee /etc/systemd/system/docker.service.d/proxy.conf > /dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,registry.local"
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl show --property=Environment docker

Composeサービスについては、アプリケーションが独自にHTTPプロキシを解釈する場合だけ環境変数を使う方法もあります。しかし本記事の透過方式では、まずコンテナへ環境変数を渡さずに接続を確認します。環境変数を追加する場合は、透過リダイレクトの対象からそのコンテナを外すか、明示プロキシの経路を一つに統一してください。

通信主な実行主体確認する設定
docker pullDocker DaemonDaemonのプロキシ、またはホスト側の透過処理
コンテナ内のcurlコンテナプロセスDockerブリッジのiptables、Clashのルール
Gitやnpmの実行各アプリケーション透過処理、証明書、必要なら環境変数
コンテナ間接続Docker内部ネットワークプライベートCIDRの除外、名前解決

開発通信をClashルールで振り分ける

透過プロキシは通信をClashへ渡すだけなので、最終的な経路は通常のClashルールで決まります。Docker HubやGitHubだけをプロキシ対象にし、社内レジストリ、ローカルネットワーク、社内Gitは直接接続するという構成が扱いやすいでしょう。ルールは上から順番に評価されるため、例外を対象ルールより前に置きます。

Docker開発環境向けルール例
rules:
  # ローカル・社内宛ては最初に除外
  - DOMAIN-SUFFIX,local,DIRECT
  - DOMAIN-SUFFIX,internal,DIRECT
  - 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

  # 開発関連サービスはプロキシグループへ
  - DOMAIN-SUFFIX,docker.io,PROXY
  - DOMAIN-SUFFIX,docker.com,PROXY
  - DOMAIN-SUFFIX,github.com,PROXY
  - DOMAIN-SUFFIX,githubusercontent.com,PROXY
  - DOMAIN-SUFFIX,pypi.org,PROXY
  - DOMAIN-SUFFIX,npmjs.org,PROXY

  - MATCH,DIRECT

Docker Hubへの接続では、認証やマニフェスト取得のために複数のホスト名が使われることがあります。docker.ioだけを登録しても、認証エンドポイントやCDNのドメインが別経路になる場合があるため、Clashの接続ログで実際の宛先を確認してください。広すぎるワイルドカードを追加するより、必要なドメインをログから段階的に追加する方が安全です。

また、IPルールを先に置くと、fake-ipやドメインルールが期待どおりに動かない場合があります。ドメイン単位で制御したい開発サービスでは、DNSモード、ルール順序、no-resolveの有無を一緒に確認してください。

接続できないときの確認手順

設定後に通信できない場合は、コンテナ、ホスト、Clashの三つの層を分けて確認します。最初から設定を大きく変更するのではなく、各層で一つずつ事実を確認することが重要です。

  1. コンテナ内でip routeを実行し、デフォルトルートがDockerゲートウェイを向いているか確認します。
  2. getent hosts github.comnslookupで、DNS解決が成功しているか確認します。
  3. curl -v https://github.comを実行し、TLSハンドシェイク前後のどこで停止するかを確認します。
  4. Clashの接続画面またはログで、コンテナの通信が表示され、想定したルールとプロキシグループに一致しているか確認します。
  5. ホスト上でsudo iptables -t nat -L CLASH_DOCKER -n -vを実行し、パケットカウンターが増えているか確認します。
  6. Docker Daemonのログをjournalctl -u dockerで確認し、イメージ取得だけが失敗していないか切り分けます。
通信ループに注意:Clashのプロセス自身がiptablesのリダイレクト対象になると、Clashが外部ノードへ接続する通信までClashへ戻り、接続が急激に遅くなったりタイムアウトしたりします。専用ユーザーによる除外、Clashのプロセス確認、ルールカウンターの監視を必ず行ってください。

権限エラーが出る場合は、iptablesの変更にroot権限が必要です。MihomoのTUNやTPROXYを使う場合は、ソケット作成やルーティング変更に追加の権限が必要になることがあります。DockerコンテナへNET_ADMINや特権モードを安易に付与するのではなく、原則としてホスト側でClashを動作させ、必要最小限の権限だけを与えてください。

Docker Desktop for macOSとWindowsでは、Docker Engineが軽量な仮想マシン内部で動作します。そのため、ホストOSのiptablesへルールを追加しても、LinuxのネイティブDockerと同じようにコンテナ通信を捕捉できない場合があります。この環境では、Docker Desktop側のプロキシ設定、Clashのホスト公開設定、または仮想マシン内部での透過設定を検討してください。まずは明示的なDaemonプロキシで動作を確認し、透過方式が本当に必要か判断するのが現実的です。

安全に運用するための設計ポイント

開発環境では動けばよいと思いがちですが、透過プロキシはホスト全体の通信経路を変更できるため、運用ルールを決めておく必要があります。Clashの管理APIは、特別な理由がなければ127.0.0.1へバインドし、LANへ公開する場合は強力なシークレットを設定してください。allow-lanを有効にして透過ポートを公開する構成では、信頼できないネットワークからの接続をファイアウォールで遮断します。

  • iptablesの変更前に現在のルールを保存し、元へ戻す手順を用意する。
  • Docker内部CIDR、社内CIDR、Clash自身の通信を明確に除外する。
  • プロキシ対象はDocker Hubなど必要なドメインから始め、広すぎるルールを避ける。
  • DNSの経路と名前解決を確認し、社内ドメインを外部DNSへ送らない。
  • Clashの設定変更後は、通常のホスト通信とコンテナ通信を別々にテストする。
  • ネットワーク構成を変更したときは、Composeのネットワーク名やサブネットも再確認する。

実際の導入では、まずMihomoの混合ポートを使った明示プロキシでノードとルールが正常に動くことを確認し、その後にredir方式を追加すると失敗原因を追跡しやすくなります。Docker Hubのログイン、イメージのpull、コンテナ内からのHTTPSアクセス、社内サービスへの直接接続という四つのテストを通せば、開発に必要な基本経路をほぼ確認できます。

コンテナごとに環境変数を配布する運用から脱却したい場合でも、すべての通信を無条件にプロキシへ送る必要はありません。Dockerブリッジの範囲を限定し、ClashのドメインルールとDNS設定を組み合わせることで、開発に必要な外部通信だけを安定して振り分けられます。構成を小さく始め、ログとパケットカウンターを見ながら拡張することが、トラブルの少ないDocker開発環境への近道です。

はじめる

Clash でトラフィックを完全制御

Windows・macOS・Linux・Android・iOS 対応。柔軟なルール、すぐに使えます。

無料ダウンロード セットアップガイドを見る →
はじめる

Clash でトラフィックを完全制御

Windows・macOS・Linux・Android・iOS 対応。柔軟なルール、すぐに使えます。

無料ダウンロード セットアップガイドを見る →
Clash をダウンロード
全プラットフォーム対応。ワンクリックでトラフィック制御、複雑な設定不要。
無料ダウンロード