Old-School Networking / 2026 ・ No.11

いにしえのネットワーク入門

第11回 開発者のネットワーク ── localhost、Docker の中と外、プロキシ

公開

Wireshark の画面。docker0 で記録した 10 個のパケットで、4番の GET /api/whoami HTTP/1.1 が選ばれている。明細の Internet Protocol Version 4 を開くと Source Address: 192.168.1.20、Destination Address: 172.17.0.2、Time to Live: 63。右のバイトの欄には Host: 192.168.1.10:8080 の文字が見える
スマホが PC の 8080 番に送った頼みごとを、コンテナの手前(docker0)で記録したもの。宛先は 172.17.0.2(コンテナ)の 3000 番に書きかわっていますが、HTTP の Host は 192.168.1.10:8080 のまま。PC がルーターの役をしたので、TTL は1つ減って 63 です(筆者がクラウド上の Linux を開発者の PC に見立てて、2026年10月25日に記録したものを Wireshark 4.2.2 で開いて撮影。画面は英語のまま。ウインドウの上の帯と見出しは説明のために描き足したもの)

第10回までで、申請ボタンの1回の通信を、ケーブルから HTTP までたどりました。今回は、毎日いちばん長くいるのに、いちばん迷う場所、開発者の PC の中を歩きます。
localhost とは何か、「スマホから見えない」はなぜ起きるか、Docker のコンテナはどこにあるのか、会社のプロキシの向こうへはどう出るのか、開発サーバーのプロキシはなぜ CORS を消すのか。Docker と練習用ネットワークで、ひとつずつ確かめます。

一

いちばん迷う場所

新井ケーブルから HTTP まで、地図は全部わかりました。なのに、ぼくの localhost:3000 がどこにあるのかはわかりません。

古井地図の外じゃない。地図の中に、小さな地図が入れ子になっているんだ。PC の中にもう1台の PC(コンテナ)、その中にまた localhost。

新井マトリョーシカですか。

古井そう。しかも人形ごとに「自分」の呼び方が同じ localhost なんだ。迷うに決まっている。

全12回の路線図(いまここ:第11回)

1 全体図 2 物理層 3 データリンク層 4 IP アドレス 5 ルーター 6 TCP 7 UDP・ソケット 8 TLS 9 DNS 10 HTTP 11 開発者の網 12 切り分け いまここ 全体図 第1層 第2層 第3層 第4層 第5・6層 第7層 実践
  • localhost と待ち受けの住所 ── 127.0.0.1 で待つとスマホからは届かない、0.0.0.0 なら届く(実測)
  • Docker ── コンテナは PC の中のもう1台の PC。-p は宛先の書きかえ(実測と Wireshark)
  • コンテナの中の localhost ── DB に localhost でつながらない理由と、名前で呼ぶ方法(実測)
  • プロキシ ── 会社のプロキシの CONNECT、NO_PROXY、開発サーバーのプロキシで CORS が消えるわけ(実測)

練習には、クラウド上の Linux を「開発者の PC」に見立てました。この PC の上で Docker 29 を動かし、家の LAN にスマホ役(192.168.1.20)を、別の場所に「会社」(社内の PC とプロキシ)をネットワーク名前空間で作っています。申請の API は、だれから届いたか(you)と、自分はだれか(me)を返す練習用のものです。

二

localhost は名前

PC には、外とつながる口(LAN の口や Wi-Fi)のほかに、自分自身へ折り返すだけの口、ループバック(lo)があります。住所は 127.0.0.1。実は 127 で始まる住所はすべてループバックで、練習の PC でも 127.0.0.2 に送ると自分に届きました。ここに送ったパケットは、ケーブルにも電波にも出ません。

そして localhost は名前です。第9回の名前と同じく、OS の hosts などで住所に変わります。ふつうは 127.0.0.1 ですが、IPv6 のループバック ::1 にもなりえます。練習のクラウドの Linux は IPv6 を持たないので、ここだけ別の Linux(Ubuntu 22.04)で試しました。

# サーバーを ::1(IPv6 のループバック)だけで待たせ、localhost で呼ぶ
# curl は両方を試す
pc$ curl -v http://localhost:18011/
    *   Trying 127.0.0.1:18011...
    * connect to 127.0.0.1 port 18011 failed: Connection refused
    *   Trying ::1:18011...
    * Connected to localhost (::1) port 18011
    < HTTP/1.0 200 OK
# Node.js の fetch は 127.0.0.1 だけを試す
pc$ node -e "fetch('http://localhost:18011/')"
    ECONNREFUSED
# ちなみに、この PC の hosts では localhost は 127.0.0.1 だけ
pc$ grep localhost /etc/hosts
    127.0.0.1 localhost
    ::1 ip6-localhost ip6-loopback

localhost は、道具によって行き先の試し方が違う

localhost ただの名前 OS の hosts など 名前 → 住所 127.0.0.1 IPv4 ::1 IPv6 curl:127.0.0.1 も ::1 も、順に試す Node.js の fetch:引けた住所だけ(この PC では 127.0.0.1) 迷ったら、名前ではなく 127.0.0.1 と住所で書く

同じ「localhost」なのに、curl は 127.0.0.1 で断られたあと ::1 を試してつながり、Node.js の fetch は 127.0.0.1 だけを試して ECONNREFUSED になりました。サーバーが ::1 でだけ待ち、呼ぶ側が 127.0.0.1 を見に行く。「サーバーは動いているのにつながらない」の典型です。

POINT

設定ファイルに書く宛先は、名前の localhost より、住所の 127.0.0.1 のほうが迷いません。この回の開発サーバーの設定でも、そう書いています(第8章)。

三

待ち受けの住所:127.0.0.1 と 0.0.0.0

サーバーは、番号(ポート)だけでなくどの口で待つかも決めて待ちます。同じ API を、127.0.0.1 の 3000 番と、0.0.0.0 の 3001 番で起動しました。

# 同じ API を 127.0.0.1:3000 と 0.0.0.0:3001 で待たせる
pc$ ss -ltn
    Local Address:Port
    127.0.0.1:3000
    0.0.0.0:3001
# PC 自身から
pc$ curl http://localhost:3000/api/whoami
    {"you":"127.0.0.1","me":"pc","listen":"127.0.0.1:3000"}
pc$ curl http://127.0.0.2:3001/api/whoami
    {"you":"127.0.0.1","me":"pc","listen":"0.0.0.0:3001"}
# 同じ LAN のスマホから
phone$ curl http://192.168.1.10:3000/api/whoami
    curl: (7) Failed to connect to 192.168.1.10 port 3000 after 0 ms
phone$ curl http://192.168.1.10:3001/api/whoami
    {"you":"192.168.1.20","me":"pc","listen":"0.0.0.0:3001"}
# 3001 番で、もう1つ起動すると
    Error: listen EADDRINUSE: address already in use 0.0.0.0:3001
# だれが 3001 番を使っているか
pc$ ss -ltnp 'sport = :3001'
    0.0.0.0:3001   users:(("node",pid=748,fd=21))

どの口で待つかで、届く相手が変わる

開発者の PC アプリ :3000 / :3001 127.0.0.1 内側の口(lo) 192.168.1.10 LAN の口 スマホ .20 127.0.0.1:3000 で待つ → 内側の口だけ。スマホからは断られた 0.0.0.0:3001 で待つ → すべての口。スマホからも届いた 0.0.0.0 は「どの口でも」という待ち方の印で、宛先の住所ではない

127.0.0.1 で待つと、内側の口でしか待っていないので、同じ LAN のスマホからは断られました(PC 自身でも、LAN の住所 192.168.1.10 あてだと断られます)。0.0.0.0 は「すべての口で」という待ち方の印で、スマホから届き、API はスマホの住所 192.168.1.20 を見ています。

スマホで画面を確かめたいときに困るのがこれです。開発サーバーの Vite は、ふだん localhost だけで待ち、--host を付けると LAN にも開きます。

# --host を付けると、LAN にも公開される(Network の行が出る。一部)
pc$ npx vite --host --port 5174
    ➜  Local:   http://localhost:5174/
    ➜  Network: http://192.168.1.10:5174/
    ➜  Network: http://172.17.0.1:5174/
    ➜  Network: http://172.18.0.1:5174/
phone$ curl http://192.168.1.10:5174/   → 200
phone$ curl http://192.168.1.10:5173/   → curl: (7) Failed to connect

Network の行には、docker0(172.17.0.1)など PC が持っている口がすべて並びます。スマホから届くのは、同じ LAN の 192.168.1.10 だけです。

POINT

0.0.0.0 で待つのは「この PC のどの口からでもどうぞ」という意味です。カフェの Wi-Fi では、同じ Wi-Fi の見知らぬ人にも開いています。確かめ終わったら閉じましょう。番号の取り合い(EADDRINUSE)が起きたら、ss -ltnp などで、だれが先に座っているかを見ます。

四

Docker:コンテナはもう1台の PC

API をコンテナに入れて、-p 8080:3000 で起動しました。中と外から、まわりを見てみます。

# API をコンテナで動かし、PC の 8080 番をコンテナの 3000 番につなぐ
pc$ docker run -d --name nw11-api -p 8080:3000 … node api11.mjs
# コンテナの中から見た自分
api$ ip -4 addr; ip route
    inet 127.0.0.1/8 scope host lo
    inet 172.17.0.2/16 … eth0
    default via 172.17.0.1 dev eth0
# PC の側:docker0 という橋と、住所の書きかえの決まり
pc$ ip -4 addr show docker0
    inet 172.17.0.1/16 … docker0
pc$ iptables -t nat -S DOCKER
    -A DOCKER ! -i docker0 -p tcp --dport 8080
       -j DNAT --to-destination 172.17.0.2:3000
pc$ ss -ltnp 'sport = :8080'
    0.0.0.0:8080   users:(("docker-proxy",…))
# PC から / スマホから
pc$ curl http://localhost:8080/api/whoami
    {"you":"172.17.0.1","me":"api","listen":"0.0.0.0:3000"}
phone$ curl http://192.168.1.10:8080/api/whoami
    {"you":"192.168.1.20","me":"api","listen":"0.0.0.0:3000"}

コンテナは PC の中のもう1台の PC。-p は宛先の書きかえ

開発者の PC コンテナ nw11-api eth0 172.17.0.2 lo 127.0.0.1 アプリは 0.0.0.0:3000 で待つ veth docker0 172.17.0.1 192.168.1.10:8080 LAN の口 スマホ -p 8080:3000 → 172.17.0.2:3000 コンテナは、PC の中にあるもう1台の小さな PC。自分の住所と localhost を持つ -p は、PC の番号に来た頼みごとの宛先を、コンテナの住所に書きかえる(第5回の NAT)

コンテナの中には、自分の eth0(172.17.0.2)と、自分の lo(127.0.0.1)がありました。外への道は 172.17.0.1、つまり PC の上に Docker が作った docker0 という橋(第3回のスイッチの仲間)です。コンテナは、PC の中に置かれたもう1台の小さな PC なのです。

では -p 8080:3000 は何をしたか。PC の決まりに「8080 番に来たら、宛先を 172.17.0.2 の 3000 番に書きかえる」(DNAT)が1行増えていました。第5回の家のルーターの NAT と同じしくみで、今回は PC がルーターの役をしています。

Wireshark のパケット一覧を2つ縦に並べたもの。上は家の LAN 側で、192.168.1.20 と 192.168.1.10 の 8080 番の間の 10 個。下は docker0 側で、192.168.1.20 と 172.17.0.2 の 3000 番の間の 10 個。どちらも4番が GET /api/whoami、6番が HTTP/1.1 200 OK
同じ1回の頼みごとを、PC の入口と、コンテナの手前の2か所で記録しました。送り元はスマホのまま、宛先だけが PC の 8080 番からコンテナの 3000 番に書きかわっています(筆者がクラウド上の Linux を開発者の PC に見立てて、2026年10月25日に記録したものを Wireshark 4.2.2 で開いて撮影。画面は英語のまま。ウインドウの上の帯と見出しは説明のために描き足したもの)

スマホの頼みごとは、PC の入口では 192.168.1.10 の 8080 番あて、コンテナの手前では 172.17.0.2 の 3000 番あて。送り元はスマホのままなので、API は「192.168.1.20 から来た」と答えました。一方、PC 自身から localhost:8080 に送ると、答えは「172.17.0.1 から」。こちらは docker-proxy という Docker の中継役が受け取り、代わりにつなぎ直しているからです。

POINT

Mac や Windows の Docker Desktop では、コンテナは PC の上ではなく、Docker が用意した Linux の仮想マシンの中で動きます。docker0 もその中にあるので、PC から 172.17.0.2 に直接は届きません。-p で公開した番号を使うのが、どこでも通じるやり方です。

五

コンテナの中の localhost

コンテナが「もう1台の PC」なら、コンテナの中の localhost はそのコンテナ自身です。ここから、よくある3つのつまずきを再現しました。

# ① コンテナの中のアプリが 127.0.0.1 で待っている(-p 8081:3000)
pc$ curl http://localhost:8081/api/whoami
    curl: (56) Recv failure: Connection reset by peer
phone$ curl http://192.168.1.10:8081/api/whoami
    curl: (7) Failed to connect to 192.168.1.10 port 8081 after 0 ms
# ② 公開を PC の中だけにする(-p 127.0.0.1:8082:3000)
pc$ curl http://localhost:8082/api/whoami
    {"you":"172.17.0.1","me":"local","listen":"0.0.0.0:3000"}
phone$ curl http://192.168.1.10:8082/api/whoami
    curl: (7) Failed to connect to 192.168.1.10 port 8082 after 0 ms
# ③ コンテナから、PC で動いている開発サーバーへ(3000=127.0.0.1、3001=0.0.0.0)
pc$ docker run --add-host=host.docker.internal:host-gateway …
    localhost:3001             → 127.0.0.1    ECONNREFUSED
    host.docker.internal:3001  → 172.17.0.1   つながった
    host.docker.internal:3000  → 172.17.0.1   ECONNREFUSED

① コンテナの中のアプリが 127.0.0.1 で待つと、DNAT で eth0 に届いた頼みごとを受け取れず、PC からは「Connection reset by peer」、スマホからは「つながらない」になりました。コンテナの中のアプリは 0.0.0.0 で待たせます。② 公開を PC の中だけにしたいときは、-p 127.0.0.1:8082:3000 と口を書きます。何も書かないと、Docker はすべての口(0.0.0.0)で公開します。Docker の説明書も、そのままだと外からも見えると注意しています。③ コンテナから PC の開発サーバーを呼ぶとき、localhost はコンテナ自身なので届きません。host.docker.internal(ここでは 172.17.0.1)で PC を呼び、PC の側は 0.0.0.0 で待たせます。

API とデータベースを Docker Compose で並べると、もう1つの定番、「DB に localhost でつながらない」が起きます。

# compose.yaml:api(Node.js)と db(PostgreSQL)。db は外に公開しない
pc$ docker compose ps
    api   0.0.0.0:8080->3000/tcp
    db    5432/tcp
# api の中の「電話帳」
api$ cat /etc/resolv.conf
    nameserver 127.0.0.11
# api の中から DB につなぐ
api$ node ck11.mjs localhost:5432 db:5432 nothere:5432
    localhost:5432  → 127.0.0.1    ECONNREFUSED
    db:5432         → 172.18.0.2   つながった (2.60 ms)
    nothere:5432    名前が引けない ENOTFOUND
# 名前を決めていないふつうの docker run どうしでは、名前で呼べない
    nw11-bad:3000   名前が引けない ENOTFOUND

コンテナの数だけ localhost がある。となりは名前で呼ぶ

開発者の PC 開発サーバー :3001(0.0.0.0) PC の localhost = PC 自身 api 172.18.0.3 localhost = api 自身 db 172.18.0.2 :5432 db:5432 ○ localhost:5432 × host.docker.internal:3001 ○ コンテナの数だけ localhost がある。中の localhost は、そのコンテナ自身 となりのコンテナは名前(db)で、PC は host.docker.internal で呼ぶ 名前は、Compose の網の中の DNS(127.0.0.11)が答える

Compose は専用の網(172.18.0.0/16)を作り、コンテナどうしをサービスの名前で呼べるようにします。名前を答えるのは、各コンテナの中の 127.0.0.11 にいる Docker の DNS です。api から見て、DB は localhost:5432 ではなく db:5432。一方、名前を決めていないふつうの docker run どうし(最初からある bridge という網)では、名前では呼べませんでした。

新井DB の設定、PC で動かしていたときのまま localhost にしていました……。

古井コンテナに引っ越したら、住所録も書きかえる。人間の引っ越しと同じだ。

六

会社のプロキシ

会社では、社内の PC から外へ直接は出られず、プロキシ(代理人)を通すことがよくあります。練習の「会社」でも、社内の PC(10.0.0.10)が知っているのはプロキシ(10.0.0.8 の 3128 番、tinyproxy)だけにしました。

# 社内の PC は、外の名前も引けず、外へ出る道もない
corp$ curl https://expenses.example:3443/api/me
    curl: (6) Could not resolve host: expenses.example
# プロキシを使うよう、環境変数で伝える
corp$ export HTTPS_PROXY=http://10.0.0.8:3128
corp$ curl -v https://expenses.example:3443/api/me
    * Uses proxy env variable HTTPS_PROXY == 'http://10.0.0.8:3128'
    > CONNECT expenses.example:3443 HTTP/1.1
    < HTTP/1.0 200 Connection established
    * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519
    * ALPN: server accepted h2
    < HTTP/2 401
    {"error":"login required"}   (0.054 s)
# 暗号なしの HTTP だと、プロキシには URL ごと渡す
corp$ http_proxy=http://10.0.0.8:3128 curl -v http://expenses.example:3000/api/expenses
    > GET http://expenses.example:3000/api/expenses HTTP/1.1
    < HTTP/1.1 200 OK
    < Via: 1.1 proxy.corp.example (tinyproxy/1.11.1)

HTTPS は、プロキシに CONNECT でトンネルを掘ってもらう

社内の PC 10.0.0.10 プロキシ 10.0.0.8:3128 申請サーバー expenses.example 社内 | 外 CONNECT TCP をつなぐ トンネルの中を TLS(HTTPS)がそのまま通る HTTPS では、プロキシに伝えるのは行き先の名前と番号だけ 名前を引くのも、外へつなぐのもプロキシ。社内の PC はプロキシしか知らない 暗号なしの HTTP なら、URL ごとプロキシに渡す(GET http://…)
Wireshark の画面。社内の PC(10.0.0.10)とプロキシ(10.0.0.8 の 3128 番)の間の記録。4番 CONNECT expenses.example:3443 HTTP/1.1 が選ばれ、明細に Host: expenses.example:3443、User-Agent: curl/8.5.0、Proxy-Connection: Keep-Alive。6番は HTTP/1.0 200 Connection established、8番は Client Hello (SNI=expenses.example)、9番から TLSv1.3 の Application Data
社内の PC は、プロキシに「expenses.example の 3443 番へつないで」と頼み(4番)、「つながった」の返事(6番)のあとは、そのトンネルの中で TLS の握手を始めます(8番)。プロキシに伝わるのは行き先の名前と番号までで、申請の中身は鍵の中です(筆者がクラウド上の Linux を開発者の PC に見立てて、2026年10月25日に記録したものを Wireshark 4.2.2 で開いて撮影。画面は英語のまま。ウインドウの上の帯と見出しは説明のために描き足したもの)

プロキシなしでは、外の名前を引くことすらできません。HTTPS_PROXY を設定すると、curl はプロキシに CONNECT expenses.example:3443 と頼み、「Connection established」の返事のあと、そのトンネルの中で第8回と同じ TLS の握手を始めました。HTTPS では、プロキシに伝わるのは行き先の名前と番号だけです。暗号なしの HTTP では、GET http://… と URL ごと渡し、返事にはプロキシの Via ヘッダーが付きました。

POINT

会社によっては、プロキシが自前の証明書で HTTPS の中を確かめています。そのときは第8回の「unable to get local issuer certificate」が出ます。直し方は、会社の CA を信頼するよう道具に教えること(Node.js なら NODE_EXTRA_CA_CERTS など)。-k などで確かめを切るのではありません。

七

プロキシの落とし穴

プロキシの設定は、環境変数で道具に伝えるのが一般的です。ところが、道具ごとに読み方が少しずつ違います。

# ① 自分の PC の localhost まで、プロキシに頼んでしまう
corp$ http_proxy=http://10.0.0.8:3128 curl http://localhost:3000/api/whoami
    500 Unable to connect
    Tinyproxy was unable to connect to the remote web server.
# NO_PROXY で「ここは直接」と伝える
corp$ http_proxy=… NO_PROXY=localhost,127.0.0.1 curl http://localhost:3000/api/whoami
    {"you":"127.0.0.1","me":"corp-pc","listen":"127.0.0.1:3000"}
# ② curl は、大文字の HTTP_PROXY を http:// には使わない
corp$ HTTP_PROXY=http://10.0.0.8:3128 curl http://expenses.example:3000/api/expenses
    curl: (6) Could not resolve host: expenses.example
# ③ Node.js の fetch は、そのままでは環境変数を見ない
corp$ HTTPS_PROXY=http://10.0.0.8:3128 node -e "fetch('https://expenses.example:3443/api/me')"
    error EAI_AGAIN
# Node.js 22 では、NODE_USE_ENV_PROXY=1 で見るようになる(試験的という注意つき)
corp$ HTTPS_PROXY=… NODE_USE_ENV_PROXY=1 node -e "fetch(…)"
    Warning: EnvHttpProxyAgent is experimental, …
    ok {"error":"login required"}

① プロキシを設定したまま自分の localhost を呼ぶと、curl はそれもプロキシに頼み、プロキシはプロキシ自身の localhost を探して「Unable to connect」(500)。NO_PROXY=localhost,127.0.0.1 で「ここは直接」と伝えると届きました。② curl は、http:// のための http_proxy を小文字でしか読みません(説明書にもそう書いてあります)。大文字の HTTP_PROXY だけでは、プロキシを使わず直接出ようとして失敗しました。③ Node.js の fetch は、そのままでは環境変数を見ません。Node.js 22.21 から入った NODE_USE_ENV_PROXY=1 を付けると、試験的という注意つきで、プロキシを通りました。

新井curl は通るのに、npm も git も Docker も通らない日がありました。

古井道具ごとに、別の場所に書くんだ。npm は npm の設定、git は git の設定、Docker はデーモンの設定。1つ通ったから全部通る、とは思わないことだ。

八

開発サーバーのプロキシと CORS

第10回の CORS を、開発中の PC で再現しました。画面は Vite の開発サーバー(localhost:5173)、API は 127.0.0.1:3000 で、API は CORS の許可を返しません。Chromium で画面を開き、申請を2通りで送りました。

# 開発サーバー(Vite)を起動
pc$ npx vite
    VITE v7.3.7  ready in 217 ms
    ➜  Local:   http://localhost:5173/
    ➜  Network: use --host to expose
# 画面(5173 番)から API(3000 番)へ直接 POST(Chromium のコンソール)
    Access to fetch at 'http://localhost:3000/api/expenses'
    from origin 'http://localhost:5173' has been blocked by CORS policy:
    Response to preflight request doesn't pass access control check:
    No 'Access-Control-Allow-Origin' header is present …
    → Failed to fetch
# 開発サーバー経由で POST(/api を 127.0.0.1:3000 へ回す設定)
    POST http://localhost:5173/api/expenses
    → 201 {"id":"EXP-2026-0012","status":"submitted"}
# API の記録:直接のときは下見(OPTIONS)だけが届き、本番の POST は来なかった
    OPTIONS /api/expenses from 127.0.0.1   ← 直接
    POST /api/expenses from 127.0.0.1      ← 経由

番号が違えば別の Origin。開発サーバーに回してもらえば同じ Origin

ブラウザ 画面 localhost:5173 ブラウザの番人 Origin を比べる 開発サーバー localhost:5173 API 127.0.0.1:3000 ② /api(同じ Origin) 裏で回す ① 直接(番号が違う=別の Origin) × ① は CORS で止まった。② はブラウザから見ればずっと同じ相手 開発サーバーから API へは、ブラウザの外の話なので CORS は関係ない 本番も同じ形にするなら、Web サーバー(nginx など)で /api を回す

同じ localhost でも、番号が違えば別の Origin です。直接送ると、ブラウザは下見(OPTIONS)だけを送り、許可がないので本番の POST を送りませんでした。API の記録にも OPTIONS しか残っていません。開発サーバーの設定に proxy: { '/api': 'http://127.0.0.1:3000' } と書き、画面からは /api/expenses に送ると、ブラウザから見れば相手はずっと 5173 番。開発サーバーが裏で API に回し、201 が返りました。

POINT

開発サーバーのプロキシは「開発中だけの近道」です。本番でも画面と API を同じ Origin に見せたいなら、Web サーバー(nginx など)で /api を API に回す、同じ形を作ります。別の Origin のままにするなら、第10回のとおり、API が許可のヘッダーを返します。

九

自分の PC で確かめる

調べたいことMacWindowsLinux
待っている番号と口lsof -nP -iTCP -sTCP:LISTENnetstat -ano | findstr LISTENINGss -ltnp
3000 番の持ち主lsof -nP -i :3000netstat -ano | findstr :3000ss -ltnp 'sport = :3000'
プロキシの設定scutil --proxynetsh winhttp show proxyenv | grep -i proxy
コンテナの公開番号docker port コンテナ名
コンテナの網と住所docker network inspect bridge
コンテナの中から見るdocker exec -it コンテナ名 sh

「つながらない」と思ったら、まずどこから、どこの、どの口の何番へ送っているのかを1行で書き出してみましょう。PC から? コンテナの中から? localhost はだれのこと? それだけで半分は解けます。

結 — 一枚まとめと、次回

  1. localhost は名前。127.0.0.1 にも ::1 にもなりうる。道具によって試し方が違うので、迷ったら 127.0.0.1 と書く。
  2. サーバーは口を選んで待つ。127.0.0.1 は PC の中だけ、0.0.0.0 はすべての口(スマホからも、カフェの他人からも)。
  3. コンテナはもう1台の PC。自分の住所と localhost を持ち、docker0 の橋でつながる。-p は宛先の書きかえ(DNAT)。
  4. コンテナの中のアプリは 0.0.0.0 で待つ。公開を PC の中だけにするなら -p 127.0.0.1:…。
  5. となりのコンテナは名前で(db:5432)、PC は host.docker.internal で呼ぶ。
  6. HTTPS のプロキシは CONNECT でトンネル。NO_PROXY、小文字の http_proxy、道具ごとの設定に注意。
  7. 番号が違えば別の Origin。開発サーバーのプロキシで同じ Origin に見せれば、CORS は起きない。

新井localhost が3つも4つもあるなんて。でも、どこから送っているかを考えれば迷わなくなりました。

古井次回は最終回、「つながらない」ときの切り分けだ。11回分の地図を、下の層から順に指でなぞって、どこで止まっているかを突き止める。

参考

記録は、筆者がクラウド上の Linux(Ubuntu 24.04、カーネル 6.18)を開発者の PC に見立て、Docker 29.9.2 とネットワーク名前空間で作った練習用ネットワークで、2026年10月25日に取ったものです。API は Node.js 22.23 で書いた練習用のもの、コンテナの像は node:22-alpine と postgres:17-alpine、プロキシは tinyproxy 1.11.1、開発サーバーは Vite 7.3.7、ブラウザは Playwright の Chromium です。この Linux は IPv6 を持たないため、localhost と ::1 の実験だけは別の Linux(Ubuntu 22.04.5、curl 7.81.0)で行いました。名前と住所はすべて説明用に予約されたもの、または練習用に作ったものです。表の数字や出力はすべて、この記録とそのときのコマンドの出力から読んだ実際の値です(長い出力は一部を省いています)。登場人物は架空の人物です。

いにしえのネットワーク入門 第11回 — 開発者のネットワーク / 2026.10

← 記事一覧へ