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.
◈ DIAGRAM
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
Bash
# 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
Bash
# 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
Bash
# 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}}'PLACEMENT PRO TIP**Tip:** Bind admin and metrics ports to `127.0.0.1` explicitly: `-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 to `0.0.0.0` including ports that should never be publicly accessible.
COMMON MISTAKE / WARNING**Common Mistake:** Thinking `EXPOSE` in a Dockerfile makes the container publicly accessible. `EXPOSE` is purely documentation — it has no effect on network access. You must use `-p host:container` in `docker run` to actually publish a port.