DEFAULT_INPUT_POLICY=DROP にしてあるのに、
docker run -p 3000:3000 したサービスは LAN の誰からでも見えている。
これは設定ミスではなく、そういう仕組みになっている。
ufw が書くルールは基本的に INPUT チェインに入る。
一方 Docker の公開ポートは DNAT でコンテナ宛に書き換えられるので、
パケットは INPUT ではなく FORWARD を通る。
LAN のパケット
→ nat/PREROUTING (DOCKER チェインで DNAT)
→ FORWARD ← Docker が ACCEPT を挿入済み
→ コンテナ
※ INPUT は通らない = ufw の DROP が効かない
しかも Docker は自分のルールを ufw より前に挿入するので、
ufw deny 3000 と書いても勝てない。
待受アドレスを見れば一目でわかる。0.0.0.0 になっているものは全部 LAN から見える。
$ ss -tln
LISTEN 0 4096 0.0.0.0:3000 0.0.0.0:* ← Grafana、LAN に丸見え
LISTEN 0 4096 0.0.0.0:9090 0.0.0.0:* ← Prometheus
LISTEN 0 4096 0.0.0.0:11434 0.0.0.0:* ← Ollama(認証なし)
ファイアウォールで塞ごうとせず、そもそも 0.0.0.0 で待ち受けないのが正しい。
compose の ports にバインドアドレスを書く。
ports:
- "3000:3000" # ✗ 0.0.0.0:3000
- "127.0.0.1:3000:3000" # ✓ ループバックのみ
これで LAN からは消える。アクセスは Tailscale 経由のリバースプロキシに一本化する。
全サービスを 127.0.0.1 に寄せたら、リバースプロキシから
host ネットワークで動いているサービス(Jellyfin)への応答が 7.6 秒かかるようになった。
ヘルスチェックも通らず、名前解決をやり直し続けていた。
理由はこうだった。
| 経路 | 通るチェイン | 結果 |
|---|---|---|
| bridge のコンテナ → ホストの公開ポート | FORWARD(Docker が ACCEPT) | 通る |
| bridge のコンテナ → ホストの非公開ポート | INPUT(ufw が DROP) | 通らない |
Jellyfin は host ネットワークで直接 8096 を持っていて、Docker の公開ポートではない。 つまりブリッジ網のプロキシコンテナから見ると「ufw に守られたホストのポート」であり、 DROP されていた。公開ポートを閉じたことで、それ自体が到達不能になったという筋。
プロキシ側を network_mode: host にした。
ホスト上で動けばループバック経由になるので ufw の INPUT を通らない。
services:
proxy:
# ufw が INPUT DROP なので、bridge 網からホストの非公開ポートに届かない。
# host 網ならループバック経由になり ufw の影響を受けない。
network_mode: host
応答は 7.6 秒から 0.053 秒になった。
ss -tln で 0.0.0.0 を数えるのを習慣にしたほうがいい。
ufw の status を見ても何もわからない。