Chapter 19Lesson 01~120 minutes

Port Publishing, NAT, Firewall Interaction, Direct Routing, Host Binding, and Exposure Security: Concepts, Architecture, and Mental Model

Trace container service exposure from the application listener through Docker publish state, host binding, firewall/NAT or direct routing, and the real client path.

Port publishingNATHost bindingFirewallExposure model

Learning objectives

  • Explain the complete exposure path from an application listener inside a container to a real client response.
  • Distinguish container listen address/port, Docker publish mapping, host bind address/port, firewall/NAT state, direct routing, and application reachability.
  • Use read-only inspection to establish container network mode, port mappings, host sockets, and platform/firewall assumptions before changing exposure.
  • Explain why EXPOSE metadata does not publish a port and why a published port does not prove an application is listening or healthy.
  • Recognize the security difference between loopback publication, all-interface publication, routed container access, and host networking.
Chapter 19 principle. “The port is open” is too vague. Prove five separate facts: the process listens → Docker has a publish rule → the host binds the intended address → the packet path/firewall permits it → the application returns the expected response.

1. Why exposure must be traced, not guessed

Chapter 18 kept all traffic inside Docker networks. Chapter 19 crosses a trust boundary: clients outside the container network may reach an application through the Docker host. This introduces independent state at the application, container endpoint, Docker daemon, host socket/firewall, routing/NAT, and external-client layers.

A container may be healthy internally while no host port is published. A publish mapping may exist while the application listens on a different port. A host socket can appear correct while a host firewall or upstream router blocks the path. Conversely, an all-interface publish can expose a service farther than intended even if the developer only tested from localhost.

2. Mental model: listener → endpoint → publish → host path → client

The application first binds an address and port inside the container network namespace. Docker then associates a container port with a host bind address/port when -p/--publish or an equivalent Compose ports declaration is used. On a native Linux bridge network, Docker normally programs firewall/NAT/PAT rules. In direct-routing designs, the client can route to a container address under controlled gateway-mode/routing policy. Docker Desktop uses its managed networking/backend path rather than exposing the Linux VM's bridge exactly like a native host.

Exposure causality
flowchart TD
  A[Application process listen address + port] --> E[Container endpoint network + container IP]
  E --> P[Docker publish state host IP:port -> container port]
  P --> H[Host bind/interface firewall + NAT/PAT or routing]
  H --> C[Client path local, LAN, routed, proxy]
  C --> R[Application response]
  X[EXPOSE metadata] -. documents intent only .-> P
            

3. State that must stay separate

State Typical evidence What it proves
Application listener in-container request, process args, application logs The process accepts on the expected container port/address.
Container network mode docker inspect HostConfig.NetworkMode Whether bridge/host/none semantics apply.
Publish mapping docker port, inspect NetworkSettings.Ports Docker recorded host-to-container publication.
Host bind ss/Get-NetTCPConnection where applicable A host-side listener/forwarding endpoint exists on the intended address.
Firewall backend/rules Docker docs + optional read-only host rule inspection How Docker enforces bridge publication/isolation on this host.
Client reachability HTTP request from the intended client location The complete network path works.
Application health expected status/body/log Reachability reached the right application, not merely a TCP socket.

4. Host bind address is part of the security policy

If you omit the host IP in a normal publish mapping, Docker publishes on all host addresses by default. That is convenient for demos and frequently wrong for databases, admin consoles, debug endpoints, and local-only development tools. Binding to 127.0.0.1 expresses a narrower local-host intent in the default NAT mode.

Do not infer “loopback-only” from a browser test performed on the Docker host. Inspect the mapping itself. Also record the Engine version: Docker documents a historical pre-28 behavior in which same-L2 hosts could reach localhost-published ports; current releases include the fix, but reproducibility requires the version to be known.

5. NAT, masquerading, and direct routing are different packet models

Default bridge gateway mode is nat. Published ports receive NAT/PAT/firewall rules and outbound container traffic normally masquerades as a host address. Docker also supports gateway modes such as routed, where no NAT/masquerade is configured for that address family and remote clients need a route to the container network; Docker still filters so that only published container ports are reachable. nat-unprotected deliberately removes that protection for unpublished ports and is unsuitable as a casual troubleshooting shortcut.

Direct routing is an infrastructure decision, not a faster spelling of -p. It requires route ownership and security review. The mandatory labs stay with default local NAT publication and inspect direct-routing concepts without changing daemon or router policy.

6. Docker and host firewalls share responsibility

On Linux bridge networks Docker creates firewall rules required for network isolation and port publishing. iptables is the default backend; Engine 29 also has an experimental nftables backend. Docker warns against disabling its rule management without replacing the required behavior. UFW/firewalld interactions also require Docker-specific understanding because packets can be diverted through Docker-managed rules before ordinary host-policy assumptions apply.

Therefore: do not “fix” a publish problem by flushing rules, disabling a firewall, or changing the daemon backend. Capture evidence first, identify the exact layer, and make the smallest authorized change.

7. EXPOSE is metadata, publication is runtime state

EXPOSE 8080 records an image author's intended listening port. It does not create a host mapping. docker run --expose behaves similarly. A runtime -p mapping can publish a port whether or not the image declares EXPOSE. -P uses exposed ports as input and assigns host ports automatically.

This distinction is valuable in incident response: image metadata can say “8080 intended,” container configuration can say “host 127.0.0.1:49153 maps to 8080,” while the process may actually be listening on 9090. Those are three separate facts.

8. Read-only baseline before changing exposure

docker version
docker info
docker context show
docker compose version || true

docker network ls
docker ps -a --no-trunc

docker pull busybox:1.36.1
docker image inspect busybox:1.36.1 --format 'ID={{.Id}} RepoDigests={{json .RepoDigests}}'
# Existing container mappings.
docker ps --format 'table {{.Names}}	{{.Status}}	{{.Ports}}'

# Replace <container> only with an object you are authorized to inspect.
docker port <container> 2>/dev/null || true
docker inspect <container> \
  --format   'Mode={{.HostConfig.NetworkMode}} Ports={{json .NetworkSettings.Ports}} Exposed={{json .Config.ExposedPorts}}' 2>/dev/null || true

# Native Linux optional read-only host evidence:
ss -lnt 2>/dev/null || true

# Windows PowerShell equivalent, run separately when useful:
# Get-NetTCPConnection -State Listen

On Docker Desktop, host-side Linux bridge/firewall state lives behind the Desktop backend/VM boundary, so Docker-visible mappings plus host connectivity tests are usually more portable evidence than looking for a native docker0 interface on Windows or macOS.

9. Exposure evidence belongs in the release record

Evidence Why it matters
Engine/CLI/context Determines daemon and version-sensitive networking behavior.
Image ID/digest + container ID Ties the exposed workload to exact artifact/runtime identity.
Listen port/address Proves the application endpoint before host publication.
Host IP/port mapping States the intended exposure boundary.
Network mode/ID Determines bridge/host/none semantics.
Firewall backend assumption Explains host packet-filtering behavior without mutating it.
Local and intended-peer test Proves actual reachability from the relevant trust zone.
Next lesson

Next: Guided Hands-On Workflow and Core Operations

Build a disposable BusyBox HTTP service, prove its internal listener first, then compare no publication, loopback publication, and all-interface publication while capturing Docker and host evidence.

Knowledge check

A container image says EXPOSE 8080. Can a LAN client reach host:8080 automatically?

Why is -p 8080:8080 broader than -p 127.0.0.1:8080:8080 by default?

A host mapping exists but the app returns connection refused internally. Which layer should be fixed first?

Does Docker Engine 29 nftables support mean you should switch a working host to nftables for this lab?

What extra requirement does direct routing introduce compared with ordinary NAT publication?

Official references and version notes

Version baseline, verified 2026-09-21.

Docker Engine 29.8.1 is the current Engine 29 release. On Linux, Docker uses iptables by default and the Engine 29 nftables backend remains experimental. Docker Desktop implements host exposure through its managed VM/backend and can differ from native Linux host rule inspection. The labs therefore make Docker API/container-visible evidence mandatory and host firewall inspection optional/read-only.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.