CertGrid CertGrid
Hands-on Lab·Podman

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

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.

Every command on this page ran on podman01.
Server NameIP AddressOSRolesCPURAMHDD
PODMAN01192.168.0.24Ubuntu 26.04 LTSPrimary Container Host2 Core4 GB50 GB

Before you start

  1. Publish the port on the pod, not the container

    The -p 8080:80 goes on podman 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  3

    Expected resultA running pod with three containers - infra, site and probe.

    Success conditionThe pod is running and the port is declared on the pod.

  2. Reach the web server on localhost

    From inside probe, fetch http://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. probe and site share one network namespace, so site's listening socket is on probe's loopback - as far as the kernel is concerned they are one host.

    127.0.0.1 works 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.

  3. The same port, from the host and from every container

    curl http://localhost:8080 on the host reaches nginx through the published port.

    Now the detail that makes the design click. podman port -a lists 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:8080

    Infra, site and probe. Even probe, which is an Alpine running sleep and 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:8080

    Expected 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.

  4. 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 pod

    Exit 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 inspect confirms 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 at pod create time, 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"4d919ca8a0e9ab39fbc7b834fe526dfdf8ad73794f9309cbb39cc55e5d2fe46e

    Expected 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

Official sources