Chapter 13 of 16 · Visual guide

Networking: port publishing and NAT

Publishing a port installs a NAT rule on the host. Understand the rewrite and every networking mystery gets smaller.

key idea / Docker itselfcontainer / good pathhost / watch outproblem / blockedneutral

The path of one request

What -p 8080:80 doesA request to host port 8080 is rewritten by a NAT rule on the host so it arrives at the container address on port 80.Browserlocalhost:8080Host eth0port 8080NAT ruleDNAT 8080 → 80docker0bridgeContainer172.18.0.2:80Replies follow the same path back, and the host un-rewrites them (connection tracking).Only the first hop uses 8080. Everything after the rule uses 172.18.0.2:80.
-p installs a DNAT rule on the host. The process in the container still listens on :80.

Header rewriting in both directions

The packet header is rewrittenInbound traffic: the destination address and port are changed from host:8080 to container:80. Outbound: the source address of a container is replaced by the host address.Inbound (published port)dst 192.168.1.20:8080DNATdst 172.18.0.2:80Outbound (container to internet)src 172.18.0.2MASQUERADEsrc 192.168.1.20Private container addresses never leave the host: outside, everything looks like the host.

Container to container

Inside a Docker network you do not need publish rules at all.

Other containers skip the published portA container on the same network reaches web with its name and container port. localhost:8080 inside the client points to the client itself.network appclientalpinewebnginx on :80http://web:80http://localhost:8080 = the client itself
Published ports are for traffic coming from outside the Docker network.

Who can reach the port

FlagReachable fromNote
-p 8080:80any interface, including your LANsame as 0.0.0.0:8080
-p 127.0.0.1:8080:80only this machinesafest for local development
-p 80a random free host portsee it with docker port
-Pevery EXPOSEd port, random host portshandy for quick tests
Publishing can bypass your firewall toolingWatch out: Docker writes its own iptables rulesa published port can be open to the LAN even if host tools like ufw say it is closed. Bind to 127.0.0.1 when in doubt.

Try it yourself

  1. Step 1 · Publish the port and curl from the host
    $ docker network create app
    $ docker run -d --name web --rm --network app -p 8080:80 nginx:1.27
    $ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080
    200
  2. Step 2 · Read the published port

    This mapping is a NAT rule on the host. The process inside still listens on port 80.

    $ docker inspect -f '{{json .NetworkSettings.Ports}}' web
    {"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"}]}
  3. Step 3 · Another container does not use that host port

    localhost:8080 inside client is client itself. The name web on port 80 is the container port, not the published one.

    $ docker run -d --name client --rm --network app alpine:3.20 sleep 300
    $ docker exec client wget -q -O - http://localhost:8080
    wget: can't connect to remote host: Connection refused
    $ docker exec client wget -q -S -O /dev/null http://web:80
      HTTP/1.1 200 OK
    $ docker stop web client
    $ docker network rm app

Recap

Quick check

Container client runs curl localhost:8080 to reach web published as -p 8080:80, and it is refused. What should it call?
Show answer

http://web:80 on a shared user-defined network. localhost is the client itself, and the published port exists on the host, not inside the network.