Publishing Ports
In one sentence: -p 8080:80 installs a destination-NAT rule at the host's packet-processing layer: anything arriving on host port 8080 gets its destination rewritten to the container's private IP and port before routing — and replies are un-rewritten on the way out.
You already know
- From Lesson 29: container IPs are private; outbound works via source NAT, inbound doesn't work at all — yet.
- From Lesson 28: the endpoint knows the container's IP on its network.
Rewriting destinations
External clients can only address the host's public IP. Publishing a port makes the host a doorman: the engine installs a DNAT (destination NAT) rule — "TCP to my port 8080 → rewrite destination to 172.18.0.2:80" — in the kernel's packet pipeline. The rewritten packet is then routed over the bridge like any other. The kernel records each rewrite in its connection-tracking table, so reply packets are automatically reverse-rewritten: the client sees replies from host:8080 and never learns the container exists. Port 8080 is claimed on the host — publishing the same host port for two containers fails; two containers each listening on container port 80 is fine (different sandboxes, different stacks).
flowchart LR CL["external client
→ 203.0.113.7:8080"] --> NIC["host NIC"] NIC --> DNAT["DNAT rule
dst → 172.18.0.2:80"] DNAT --> CT[("conntrack table
remembers the rewrite")] DNAT --> BR["bridge"] --> C["container web
172.18.0.2:80"] C -- reply --> BR2["bridge"] --> UN["conntrack: reverse-rewrite
src → 203.0.113.7:8080"] --> CL
sequenceDiagram participant CL as client participant K as host kernel (NAT) participant C as container :80 CL->>K: SYN to 203.0.113.7:8080 K->>K: DNAT match → rewrite dst to 172.18.0.2:80, record in conntrack K->>C: deliver via bridge C-->>K: reply from 172.18.0.2:80 K->>K: conntrack hit → rewrite src back to 203.0.113.7:8080 K-->>CL: reply appears to come from host:8080 note over CL: the container's private IP
never leaks to the client
Anchor these
- Publish = DNAT in + conntrack-reversed replies out; the engine only installs rules.
- Host ports are a global namespace — one publisher per port; container ports are per-sandbox.
- Unpublished containers stay unreachable from outside — private by default.
See what actually stuck.
Take the practice scenarios now.