Chapter 19Lesson 04~130 minutes

Port Publishing, NAT, Firewall Interaction, Direct Routing, Host Binding, and Exposure Security: Diagnostics, Failure Modes, Security, and Performance

Diagnose Docker exposure failures and accidental publication by preserving listener, mapping, socket, firewall, network, and client evidence before applying the smallest correction.

DiagnosticsFirewall evidenceListener stateSecurityFailure modes

Learning objectives

  • Preserve first-failure evidence before changing listeners, publish mappings, host firewall state, routes, or application configuration.
  • Diagnose accidental all-interface exposure and correct it by recreating only the disposable container with a narrower mapping.
  • Separate application binding errors from Docker publish errors and from host/client path failures.
  • Explain why UFW/firewalld/nftables/iptables behavior must be interpreted with Docker-managed rules rather than changed blindly.
  • Use performance evidence to distinguish application latency from network translation/routing overhead.
Evidence-first order. Preserve IDs/logs/mappings → confirm context/version → prove listener → inspect Docker mapping → inspect host socket/rules read-only → test intended client path → change the smallest layer → retest the same path.

1. Diagnostic sequence

  1. Record docker context show, docker version, container ID, image identity, and current docker port/inspect output.
  2. Prove the process responds from inside the container. Capture application logs.
  3. Confirm container network mode and exact HostIp/HostPort mapping.
  4. Test from the Docker host using the exact published address.
  5. If native Linux, optionally capture host socket and firewall/NAT evidence read-only.
  6. Test from the intended peer/trust zone only when authorized.
  7. Apply the least destructive correction at the first failing layer.
  8. Repeat the exact same evidence sequence.

2. Intentionally broken example: correct publish, wrong application listener

Create a disposable server on container port 9090 but publish host port 18180 to container port 8080. Docker will faithfully forward to a port where no process listens.

docker run -d \
  --name da19-broken-listener \
  --label devops-academy.lab=ch19-diag   -p 127.0.0.1:18180:8080   busybox:1.36.1 sh -c   'mkdir -p /www; echo wrong-port >/www/index.html; exec httpd -f -p 9090 -h /www'

docker port da19-broken-listener
docker inspect da19-broken-listener --format '{{json .NetworkSettings.Ports}}'
docker logs da19-broken-listener

# This should fail because 8080 has no listener inside the container:
curl -fsS http://127.0.0.1:18180/ || true

# Prove the real listener inside the container:
docker exec da19-broken-listener wget -qO- http://127.0.0.1:9090/

Interpretation: Docker's publish configuration exists; DNS is irrelevant; the application/listener port disagrees with the mapping. Preserve the evidence, then recreate only this disposable container with host 18180 mapped to container 9090.

docker rm -f da19-broken-listener

docker run -d \
  --name da19-fixed-listener \
  --label devops-academy.lab=ch19-diag   -p 127.0.0.1:18180:9090   busybox:1.36.1 sh -c   'mkdir -p /www; echo fixed-port-ok >/www/index.html; exec httpd -f -p 9090 -h /www'

curl -fsS http://127.0.0.1:18180/

3. Failure mode: database/admin port published to all interfaces

Suppose inspect shows 0.0.0.0:5432 for a development database that should be host-only. Do not start by modifying firewall rules; the Docker declaration itself is already broader than the requirement. Capture the mapping and container/image identity, then recreate the disposable workload with an explicit loopback HostIp.

For Compose, the same principle applies: change the ports model from an all-interface mapping to an explicit loopback mapping, inspect docker compose config, and reconcile only the affected project. Retest from localhost and, if available, confirm the remote path is no longer accepted.

4. Failure mode: assuming EXPOSE publishes

Evidence pattern: image inspect reports an exposed port, the service responds internally, but docker port is empty and the host cannot connect. That is not a firewall failure; there is no runtime publication. Add a deliberate publish mapping only if host access is actually required. Otherwise the absence of a host path is correct isolation.

5. Failure mode: process listens on loopback inside the container

If an application binds only to 127.0.0.1 inside its network namespace, packets delivered to the container's bridge endpoint address may not reach it. A host publish mapping cannot rewrite the application's bind scope. Confirm the listener or application configuration and, where appropriate, bind the service to its container interface/unspecified address while retaining host exposure restrictions at the Docker publish layer.

This is why application bind address and host bind address are separate: 0.0.0.0 inside a container does not automatically mean Internet exposure; host publication still controls crossing the Docker-host boundary.

6. Failure mode: treating host firewall policy as independent of Docker

Docker's Linux bridge networking uses Docker-managed packet-filter/NAT rules. Docker documentation specifically warns that disabling its rule management is inappropriate for most users and can break networking. UFW traffic can be diverted before normal UFW INPUT/OUTPUT assumptions, while firewalld integration creates Docker-specific zones/policies.

Capture docker port, host bind evidence, firewall backend assumption, and optional rule snapshots. Escalate firewall changes to the component/team that owns the host policy. Do not flush rules or switch backends as a diagnostic shortcut.

7. Failure mode: misunderstanding client/source addressing

NAT, userland/host forwarding, reverse proxies, and direct routing can change what source address the application observes. If audit logs or ACLs depend on source identity, capture packets/logs at the application and document the network mode. Direct routing can preserve more direct container addressing, while proxies may intentionally add application-layer forwarding headers. Never build security logic on an assumed source-IP path without testing the deployed architecture.

8. Performance: locate the bottleneck before changing the network model

Measure at least application response time inside the container, response through loopback publication, and response from the intended remote client. Compare CPU/resource saturation and application logs. If only the remote path is slow, investigate host/network path. If the in-container request is already slow, changing NAT to host/routed networking cannot fix application work.

For throughput-sensitive systems, benchmark representative payloads/concurrency and compare designs in a disposable environment. Record not only speed but also the changed exposure/isolation model.

9. Failure matrix

Symptom Likely first layer Evidence Least-destructive next step
Internal request fails Application/process container logs + in-container request Fix app/listener configuration.
Internal works; docker port empty Docker configuration inspect Ports/HostConfig Publish only if host access is required.
Mapping exists; localhost fails Host publication/firewall path host curl + mapping + optional socket/rules Inspect host path; do not rebuild image yet.
Localhost works; LAN fails Host firewall/interface/routing/peer path authorized LAN test + host rules/routes Fix authorized network policy/path.
LAN works but service should be local-only Exposure policy HostIp is all-interface Recreate with explicit loopback mapping.
EXPOSE present; no host mapping Expected metadata-only state image inspect + docker port No fix unless a host path is intentionally needed.

10. Cleanup diagnostics resources

docker rm -f da19-fixed-listener da19-broken-listener 2>/dev/null || true
docker ps -a --filter label=devops-academy.lab=ch19-diag
Next lesson

Next: Checkpoint Lab

Build a complete exposure matrix with internal-only, loopback, and all-interface states, capture portable and optional platform-specific evidence, and finish in the safest local-only state.

Knowledge check

A published port fails, but an in-container request to the same container port also fails. What should you avoid doing first?

Why is accidental 0.0.0.0 publication best corrected in Docker configuration rather than hidden behind an ad-hoc firewall rule?

Can a process bind to container loopback and still be assumed reachable through a bridge publish?

Why is UFW-only reasoning insufficient on a Docker Linux host?

What evidence distinguishes network overhead from application latency?

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.