Publishing ports

Docker · Practical · 8 min

Nothing inside a container is reachable from your browser until you publish its port.

Containers get their own network namespace with a private IP. A server listening on port 80 inside a container is not reachable from the host or the outside world until you publish the port:

docker run -d -p 8080:80 nginx:1.27

The format is -p [host_ip:]host_port:container_port[/protocol]. Here, host port 8080 forwards to container port 80, so http://localhost:8080 reaches nginx.

Details that matter:

  • By default the port is bound on all host interfaces (0.0.0.0 and ::), so it's reachable from your network, not just localhost. On Linux, Docker's own iptables/nftables rules mean it can bypass host firewalls such as ufw. For anything local-only, use -p 127.0.0.1:8080:80.
  • Two containers can't publish the same host port. Pick different host ports (8081:80, 8082:80) or put a reverse proxy in front.
  • -p 80 (no host port) picks a random free host port; docker port web shows the mapping.
  • -P publishes every port the image EXPOSEs to random host ports. EXPOSE alone publishes nothing.
  • UDP needs /udp: -p 5353:53/udp.

The app inside must listen on 0.0.0.0, not 127.0.0.1. A dev server bound to the container's own loopback is unreachable through a published port. Many frameworks default to localhost, so this is the number one "port is published but connection refused" cause.

Containers talking to each other don't need published ports at all; they use a shared network, covered next.

Example

docker run -d --name web -p 8080:80 nginx:1.27
curl -s localhost:8080 | head -1          # <!DOCTYPE html>

docker run -d --name admin -p 127.0.0.1:9000:80 nginx:1.27   # local only
docker run -d --name rand -p 80 nginx:1.27
docker port rand                          # 80/tcp -> 0.0.0.0:49154

# App must listen on 0.0.0.0 inside the container
docker run --rm -p 8000:8000 python:3.13 \
  python -m http.server 8000 --bind 0.0.0.0

Remember

  • -p host:container publishes a port; EXPOSE is documentation only
  • Default binding is all interfaces; use 127.0.0.1: for local-only
  • The app must listen on 0.0.0.0 inside the container
  • Container-to-container traffic doesn't need published ports

Watch out for

  • Publishing a database with -p 5432:5432 on a cloud VM and exposing it to the internet despite the firewall.

Check yourself

  1. The port is published with -p 3000:3000 but the browser gets 'connection refused'. The app logs 'listening on 127.0.0.1:3000'. Fix?
  2. What does EXPOSE 8080 in a Dockerfile do by itself?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.docker.com/get-started/docker-concepts/running-containers/publishing-ports/