Podman Pods and Shared Localhost
A sidecar reaches its web server on `http://localhost:80` with no network, no DNS and no service. The rule that follows: `-p` belongs on the pod, and using it on a container in a pod fails with exit 125.
Pods Guide 12 of 47 Beginner
- OSUbuntu 26.04 LTS (resolute)
- Podman5.7.0
- Runtimecrun 1.21
- Networknetavark 1.16.1
- TimeAbout 12 min
- Reviewed22 August 2026
Written against the versions above. Podman follows the distribution here rather than a vendor repository, so the version you get is the one Ubuntu shipped. The commands are stable across 5.x.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| PODMAN01 | 192.168.0.24 | Ubuntu 26.04 LTS | Primary Container Host | 2 Core | 4 GB | 50 GB |
Before you start
- guide 20 - this guide is the consequence of the shared network namespace demonstrated there.
curlon the host, to reach the published port from outside the pod.
-
Publish the port on the pod, not the container
The
-p 8080:80goes onpodman pod create. That will look wrong if you are used to Docker, where the port belongs to the container - step 4 is where it stops looking wrong.Then add two containers: nginx, and a plain Alpine to poke at it from inside.
bash Example session podman pod create --name web -p 8080:804d919ca8a0e9ab39fbc7b834fe526dfdf8ad73794f9309cbb39cc55e5d2fe46epodman run -d --pod web --name site docker.io/library/nginx:alpinef2eb6b4715afa0d4a3811040b0f7b48f3394dc0f25ddc8183be73cb57fc80876podman run -d --pod web --name probe docker.io/library/alpine sleep 3003c8308b0a7745ef5680ca62a2d0dae9cd1d6b0f01d25dcbb2fc84c8e12b294b2podman pod psPOD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS4d919ca8a0e9 web Running 1 second ago 3a14a2edecea 3Expected resultA running pod with three containers - infra, site and probe.
Success conditionThe pod is running and the port is declared on the pod.
-
Reach the web server on localhost
From inside
probe, fetchhttp://localhost:80. nginx answers.Nothing was configured to make this work. There is no user-defined network, no container name resolution, no service object and no link.
probeandsiteshare one network namespace, sosite's listening socket is onprobe's loopback - as far as the kernel is concerned they are one host.127.0.0.1works identically, which is the same fact stated without DNS.This is the sidecar pattern in its simplest form, and it is why pods exist: a log shipper, a proxy or a metrics exporter can talk to the application over loopback, which is fast, needs no service discovery, and is not reachable from anywhere else.
bash Example session podman exec probe wget -qO- http://localhost:80<!DOCTYPE html><html><head><title>Welcome to nginx!</title>podman exec probe wget -qO- http://127.0.0.1:80<!DOCTYPE html><html><head><title>Welcome to nginx!</title>Expected resultThe opening lines of nginx's default page, twice. Output is truncated here to the first few lines; the real response is the whole page.
Success conditionOne container fetched a page from another over loopback.
-
The same port, from the host and from every container
curl http://localhost:8080on the host reaches nginx through the published port.Now the detail that makes the design click.
podman port -alists three containers, and every one of them reports the same mapping:3a14a2edecea 80/tcp -> 0.0.0.0:8080 f2eb6b4715af 80/tcp -> 0.0.0.0:8080 3c8308b0a774 80/tcp -> 0.0.0.0:8080Infra, site and probe. Even
probe, which is an Alpine runningsleepand listens on nothing at all, reports port 8080. The mapping is not a property of any container - it belongs to the network namespace they share, so each of them truthfully reports it.bash Example session curl -s http://localhost:8080<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>html { color-scheme: light dark; }podman port site80/tcp -> 0.0.0.0:8080podman port -a3a14a2edecea 80/tcp -> 0.0.0.0:8080f2eb6b4715af 80/tcp -> 0.0.0.0:80803c8308b0a774 80/tcp -> 0.0.0.0:8080Expected resultnginx's page from the host, then the same mapping reported by all three containers.
Success conditionYou can explain why a container that listens on nothing reports a published port.
-
Why the port had to go on the pod
Try to do it the Docker way - add a container to the running pod and publish a port on it:
Error: invalid config provided: published or exposed ports must be defined when the pod is created: network cannot be configured when it is shared with a podExit 125, and the error names the reason exactly. Network configuration belongs to whoever owns the namespace, and that is the infra container. A container joining an existing namespace has nothing left to configure - the interfaces, the addresses and the port mappings were all decided when the pod was created.
podman pod inspectconfirms where the mapping actually lives:ports=map[80/tcp:[{0.0.0.0 8080}]]It is under
InfraConfig. Practical consequence: you cannot add a port to a running pod. Decide the ports atpod createtime, or recreate the pod - which is exactly the constraint a Kubernetes Pod has, and for the same reason.bash Example session podman run -d --pod web -p 9090:90 --name late docker.io/library/alpine sleep 60Error: invalid config provided: published or exposed ports must be defined when the pod is created: network cannot be configured when it is shared with a pod[exit 125]podman pod inspect web --format 'ports={{.InfraConfig.PortBindings}}'ports=map[80/tcp:[{0.0.0.0 8080}]]podman pod rm -f webtime="2026-08-22T11:04:21Z" level=warning msg="StopSignal SIGTERM failed to stop container probe in 10 seconds, resorting to SIGKILL"4d919ca8a0e9ab39fbc7b834fe526dfdf8ad73794f9309cbb39cc55e5d2fe46eExpected resultExit 125 with the ports error, then the mapping shown under the pod's infra config.
Success conditionYou have seen the rule enforced rather than just stated.
Troubleshooting
curl: (7) Failed to connect to localhost port 8080from the host.Why: The pod is running but nothing inside it is listening on the target port, or the container that listens exited.
Fix:
podman ps --podto confirm the web container isUp, thenpodman logs site. The published port exists as soon as the pod does, whether or not anything is behind it.Error: ... port is already allocatedwhen creating the pod.Why: Another pod, container or host process already holds that port.
Fix:
podman port -alists what Podman has published;ss -ltnpcovers the rest of the machine.You need to add a port to a pod that is already running.
Why: Not supported. Port bindings are fixed at pod creation.
Fix:Recreate the pod with the full set of ports. If the pod's definition is worth keeping,
podman kube generatewrites it out first - see the next guides in this track.Publishing a privileged port such as
-p 80:80fails as a normal user.Why: Rootless processes cannot bind below 1024.
Fix:Publish a high port, or lower the threshold with
sudo sysctl net.ipv4.ip_unprivileged_port_start=80. The Networking track covers the trade-off.