ブラウザは速いのに、docker pull だけがタイムアウトする理由

多くの開発環境では、Clash や Mihomo 互換クライアントがシステムプロキシTUNでブラウザや一部アプリの通信をすでに吸い上げています。それでも docker pulldocker build 内のパッケージ取得だけが極端に遅い、あるいは TLS ハンドシェイクのあたりで切れる、というとき、第一に疑うべきはトラフィックの主体が別プロセスであることです。Docker Engine は通常、ユーザーのシェルで export した HTTP_PROXY をそのまま引き継ぎません。イメージの取得はデーモンが行い、Dockerfile の RUN apt-get などはビルド用コンテナが行います。どちらにも明示的にプロキシを渡さない限り、レジストリや外部リポジトリへの経路は「直出し」のまま残りやすいです。

第二に、仮想化の層によっては、コンテナや Linux VM から見た「自分自身の 127.0.0.1」が、ホストで待ち受けている Clash のポートと一致しません。Docker Desktop(Windows/macOS)では host.docker.internal という特別な名前でホストを指せますが、WSL2 のシェルだけを触っている場合と、Docker が動いているネットワーク名前空間は別物です。WSL2 から apt/git をホストの Clash に向ける記事と本稿は隣接テーマですが、こちらはコンテナランタイムと Docker デーモンに焦点を当てます。

💡
本稿の前提 ホスト OS 上で Clash が起動しており、mixed-port または HTTP 用の port が実際にリッスンしていること。ポート番号は環境ごとに違うため、UI または設定 YAML で 7890 など自分の値に読み替えてください。

Clash 側:mixed-port と Allow LAN(allow-lan)

コンテナや別 VM からホストの IP へ TCP で接続する場合、Clash が 127.0.0.1 にだけバインドしていると接続拒否になります。クライアントの Allow LAN(設定では allow-lan: true に相当することが多い)を有効にし、待受が 0.0.0.0 側に広がっているか確認します。これは同一マシン内の別インターフェースから届くパケットを受け入れる話であり、LAN 共有の記事で触れている考え方と根は同じです。Windows では初回接続時に Defender ファイアウォールの許可ダイアログが出ることもあるため、開発用プロファイルでブロックされていないかも合わせて見ます。

プロトコルは、HTTP プロキシとして Clash の HTTP ポート(または mixed)を使うのが分かりやすいです。接続先 URL は http://HOST:PORT 形式で統一し、HTTPS_PROXY にも同じ HTTP プロキシを指す書き方が一般的です(CONNECT メソッドで TLS を張るため)。SOCKS5 を使う場合はツールによっては socks5h:// で名前解決位置を揃える必要がありますが、Docker の公式ドキュメントが例示する環境変数は HTTP 系が中心なので、本稿では HTTP 経路を主に扱います。

デーモンのプロキシと、docker build 内のプロキシは別物

docker pulldocker pushは Docker デーモンがレジストリと話します。したがって効くのはデーモン起動環境のプロキシ設定です。一方、docker build の各 RUN ステップはビルド用コンテナ内で実行されるため、BuildKit 有効時はビルド時に渡す build-argやデーモンのデフォルト、Dockerfile 内の ENV の組み合わせで決まります。「pull は速いが build の apt だけ死ぬ」「逆に pull だけ遅い」といった切り分けは、この二層を分けて考えると早いです。

社内のプライベートレジストリや localhost 上のキャッシュだけ直結させたい場合は NO_PROXY にホスト名や CIDR を列挙します。カンマ区切りが一般的ですが、実装差があるため公式ドキュメントと Engine のバージョンを確認してください。余計な経路に載せないほうが安定なトラフィックは、意図的にプロキシ対象から外す設計が長く運用しやすいです。

Docker Desktop(Windows/macOS):host.docker.internal

Docker Desktop では、コンテナからホストへ向ける定番のホスト名が host.docker.internal です。mixed-port が 7890 なら、プロキシ URL は次の形になります(ポートは読み替え)。

http://host.docker.internal:7890

デーモン全体に効かせるには、Docker Desktop の設定画面から Resources → Proxies のようにプロキシを指定する方法と、デーモン設定 JSON(Linux 系の daemon.json に相当する UI 経由の設定)を使う方法があります。手元のバージョンでラベルが多少異なるため、画面上の「Manual proxy」「Use system proxy」のトグルと、実際に docker info で反映されているかを照合すると安全です。

ビルドだけプロキシしたい場合の例です(BuildKit 想定、値は環境に合わせて変更)。

docker build \
  --build-arg HTTP_PROXY=http://host.docker.internal:7890 \
  --build-arg HTTPS_PROXY=http://host.docker.internal:7890 \
  --build-arg NO_PROXY=localhost,127.0.0.1,::1 \
  -t myimg:latest .

Dockerfile 側で ARG HTTP_PROXYENV HTTP_PROXY=$HTTP_PROXY を宣言しておくパターンもよく使われます。そうしておけば、build-arg を渡したビルドだけがプロキシを継承し、ローカル検証用のイメージと本番用イメージを分けやすくなります。

Linux ネイティブ Docker:systemd の drop-in でデーモンに環境変数

Ubuntu などで Docker CE をそのまま入れている場合、docker サービスは systemd 管理下にあることが多いです。デーモンに HTTP_PROXY を渡すには、/etc/systemd/system/docker.service.d/http-proxy.conf のような drop-in を作成します(ディレクトリが無ければ作成)。

[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,docker-registry.example.com"

ホスト上の Clash がループバックで待っているなら 127.0.0.1 で足ります。逆に rootless やネットワーク分離が絡む構成では、ブリッジのゲートウェイやホスト側の実 IP を調べる必要が出ます。編集後は sudo systemctl daemon-reloadsudo systemctl restart docker を忘れずに。反映確認は sudo systemctl show --property=Environment docker などが手軽です。

ビルド時については、前述の --build-arg に加え、チーム全体で揃えるなら ~/.docker/config.jsonproxies ブロック(クライアント設定)を検討します。ただし挙動は Docker のバージョンとコンテキストに依存するため、ドキュメントの該当節を一度通読してから本番 CI に持ち込むのが無難です。

レジストリミラーと「イメージ加速」設定の位置づけ

プロキシは「出口を Clash 経由にする」手段であり、別の最適化としてミラーや Docker Desktop が提供する地域向けのレジストリミラー設定があります。企業ポリシーや利用規約、ミラーの信頼性を踏まえ、自分の環境で許容される組み合わせを選んでください。プロキシでレイテンシが改善するケースと、ミラーの方が速いケースは切り分け可能なので、docker pull のログに出る実ホスト名を見ながら並行して試すと判断が早いです。

いずれの方法も、イメージの出所と改ざんリスクは自分で管理する前提です。公式レジストリのまま Clash で出口だけ揃えるのが一番素直なパターンであり、ミラーは「許可されたキャッシュ」として追加するイメージです。

TUN モードとの関係

ホスト OS 全体のトラフィックを TUN で捕捉している場合でも、Docker の仮想ネットワークは別ブリッジに乗るため、自動では追従しないことがあります。そうしたときに「とりあえず確実に通す」手段が、本稿のような明示的な HTTP プロキシです。TUN の概念整理はTUN モードの解説に任せ、ここではコンテナ開発の現場でそのままコピペしやすい環境変数ルートに絞ります。

トラブルシューティング早見表

接続拒否(connection refused)

ホスト名は合っているがポートで拒否されるときは、Clash のバインドと Allow LAN を疑います。Windows なら 7890 占有と mixed-port の記事と合わせ、本当に期待したプロセスが待受けているかを確認します。

タイムアウトが続く

プロキシ URL の誤り、DNS の解決先、ノード側の不安定さ、レジストリ自体のレート制限などが重なります。Clash の接続ログで、ターゲットホストが想定ノードに流れているかを見ると、プロキシ以前/以後の切り分けがしやすいです。手順は接続ログの見方を参照してください。

build だけ失敗する

デーモンには届いているが BuildKit のステージで失敗する場合、--build-argDockerfileENV が不足していることが多いです。マルチステージビルドではステージごとに ARGENV を再宣言する必要がある点にも注意します。

まとめ

Docker と Clash を同居させるときのコツは、デーモンとビルドコンテナのどちらにプロキシを渡すかを混同しないこと、そして ホストの待受を LAN 側から到達可能にすることです。Docker Desktop なら host.docker.internal、Linux ネイティブならループバックまたは実 IP と systemd、という対応表を頭に置いておけば、pull と build のどちらが詰まっているかを短時間で切り分けられます。

クライアントの導入やバイナリの入手経路は、当サイトのダウンロードページに整理してあります。オープンソースのソースコードや Issue を追う用途では各プロジェクトの GitHub も有用ですが、実行ファイルの第一入手先はサイト側の導線に寄せた方が、アーキテクチャや更新チャネルの取り違えを減らせます。環境が整ったら、→ Clash を無料ダウンロードし、ホスト側のプロキシから試すところから進めてみてください。