fivenines
30/40
Lesson 30 · Networking

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

Architecture — the doorman in the packet path
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
Sequence — one request, both rewrites
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.
Next step

See what actually stuck.

Take the practice scenarios now.