--- name: podman-netns-addressability-probe description: "Runbook to answer \"can container X actually reach service Y\" on a rootless-podman/netavark host, instead of assuming localhost works everywhere; includes the probe commands and the false-positive traps." --- # Podman netns addressability probe Answer **"can this container actually dial that service?"** per *network namespace*, not per host. On rootless podman + netavark, a bridge-net container cannot reach host-netns loopback listeners at all, so `localhost:PORT` reasoning is wrong for it. ## Decision rule - `NetworkMode=host` → the container **is** the host netns: dials everything on `127.0.0.1`, binds any free port. - bridge/named network → own netns. Only **published** ports are reachable, via the network's **gateway IP**. Unpublished host listeners are **refused instantly (RST)**, not routed — even when the listener is bound `0.0.0.0`. ## Probe sequence (all read-only, run on the host) 1. Namespace: `podman inspect --format '{{.HostConfig.NetworkMode}}'` 2. Owner of a listener: `sudo ss -ltnp 'sport = :'` (host netns only — this is why a `0.0.0.0` listener can still be unreachable from a bridge net). 3. From inside the suspect container: `podman exec cat /proc/net/route` → default route = gateway IP to dial. 4. Probe the candidate address **inside that netns**: `podman exec sh -c 'timeout 2 bash -c "exec 3<>/dev/tcp//"; echo rc=$?'` ## Interpretation (do not blur these) - **instant refusal** = nothing listening in a reachable namespace (or rootlessport rejected an unpublished port map). - **timeout** = no route to that address at all. `host.containers.internal` (`169.254.1.2`) is an alias on the **default `podman` net only**; containers on a named network time out against it rather than being refused. ## Probe-tool gotchas verified on this host (podman 5.x, netavark) - `podman network inspect --format '{{json .containers}}'` **errors** (`can't evaluate field containers in type interface {}`) — membership is not exposed there. List from the container side instead: `podman ps -a --format '{{.Names}} {{.Networks}}'` - An **empty** `Networks` field in that output = host network or a shared namespace (`--network=container:`), not "no network". - Quadlet `Network=.network` yields runtime network name **`systemd-`**; probe with `podman network ls`, never by the `.network` filename. ## Fix options when a bridge-net container must reach a host-netns listener (a) move it to `Network=host`, or (b) publish the port — (b) makes that port reachable from every namespace **and the LAN**, so it is an exposure change needing explicit operator approval, never a silent fix. Repo source of truth for this fleet: `serverAppConfigs-docs/Architecture/Container Network Namespaces.md` (commit `2e179b0`).