Port Publishing
The mechanism by which Docker exposes a container's internal port to the host network, making the container service accessible from outside using -p host_port:container_port. Docker creates iptables DNAT rules to route traffic from the host port to the container.
Port Publishing — Making Containers Accessible From Outside
What Is Port Publishing in Simple Terms?
A container has its own private network. By default, nobody outside the container can reach services running inside it — not even your browser on the same laptop. Port publishing creates a connection between a port on the host machine and a port inside the container, so external traffic can reach the container service.
Without port publishing: Browser -> localhost:8080 -> HOST -> BLOCKED Container port 80 is private, unreachable from outside With -p 8080:80: Browser -> localhost:8080 -> HOST:8080 -> DNAT -> Container:80 Traffic redirected by iptables into the containerPort Publishing Syntax
# Basic: map host port 8080 to container port 80docker run -d -p 8080:80 nginx# Access: curl http://localhost:8080 # Bind to a specific host interface (not all interfaces)docker run -d -p 127.0.0.1:8080:80 nginx# Only accessible from localhost — not from other machines on the network# Use for admin interfaces that should not be public # Let Docker choose a random host portdocker run -d -P nginx# Docker picks a random port above 32768docker port container-name# 80/tcp -> 0.0.0.0:32769 # Map multiple portsdocker run -d -p 80:80 -p 443:443 nginx # Map UDP portdocker run -d -p 53:53/udp dns-server # In Docker Composeservices: api: ports: * "8080:8080" # all interfaces * "127.0.0.1:9090:9090" # localhost only (admin/metrics)How It Works — iptables DNAT
# Docker creates iptables rules for port publishingsudo iptables -t nat -L DOCKER --line-numbers# target prot source destination# DNAT tcp anywhere anywhere tcp dpt:8080 to:172.17.0.2:80# When traffic hits host port 8080, kernel redirects to container IP:80 # EXPOSE in Dockerfile is NOT the same as port publishing# EXPOSE is documentation only — it does NOT publish the portEXPOSE 80 # tells readers "this container listens on 80" # but nobody can reach it without -pChecking Published Ports
# See all published ports for a containerdocker port my-nginx# 80/tcp -> 0.0.0.0:8080 # In docker ps outputdocker ps# PORTS# 0.0.0.0:8080->80/tcp # Detailed port configdocker inspect my-nginx \ --format '{{json .NetworkSettings.Ports}}'TipBind admin and metrics ports to
127.0.0.1explicitly:-p 127.0.0.1:9090:9090. This makes Prometheus metrics and debug endpoints accessible from the server itself (for scraping) but not from the internet. A common security oversight is publishing all ports to0.0.0.0including ports that should never be publicly accessible.
Common MistakeThinking
EXPOSEin a Dockerfile makes the container publicly accessible.EXPOSEis purely documentation — it has no effect on network access. You must use-p host:containerindocker runto actually publish a port.
Frequently Asked Questions
What's the difference between EXPOSE in a Dockerfile and -p at runtime?
EXPOSE is purely documentation — it tells other developers and tools which port the container listens on, but it does not publish anything or make the port reachable from the host. Only the `-p host_port:container_port` flag (or `-P` to auto-map all EXPOSEd ports to random host ports) actually creates the iptables DNAT rule that routes host traffic into the container's network namespace. A container can be fully functional over the Docker network with zero published ports.
Why can't two containers both publish to the same host port?
The host port is a genuinely scarce resource — binding it is exclusive, so a second `docker run -p 8080:80` fails immediately if another container already holds host port 8080. This trips people up when running multiple instances of the same image for local testing; the fix is either mapping to different host ports (-p 8081:80, -p 8082:80) or, in production, letting an orchestrator assign ports dynamically and routing through a load balancer instead of fixed host bindings.