Skip to main content

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.

◈ 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 container

Port Publishing Syntax

Bash
# Basic: map host port 8080 to container port 80
docker 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 port
docker run -d -P nginx
# Docker picks a random port above 32768
docker port container-name
# 80/tcp -> 0.0.0.0:32769
# Map multiple ports
docker run -d -p 80:80 -p 443:443 nginx
# Map UDP port
docker run -d -p 53:53/udp dns-server
# In Docker Compose
services:
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 publishing
sudo 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 port
EXPOSE 80 # tells readers "this container listens on 80"
# but nobody can reach it without -p

Checking Published Ports

Bash
# See all published ports for a container
docker port my-nginx
# 80/tcp -> 0.0.0.0:8080
# In docker ps output
docker ps
# PORTS
# 0.0.0.0:8080->80/tcp
# Detailed port config
docker inspect my-nginx \
--format '{{json .NetworkSettings.Ports}}'
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

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.

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.