Port Mapping & Expose

Port mapping is how you make containerized applications accessible from outside the Docker host. This guide covers publishing ports, the EXPOSE instruction, port binding options, and when to use host networking.

-p / --publish EXPOSE Random Ports Host Networking
Why Do We Need Port Mapping?

By default, containers have their own private network namespace. They can communicate with each other freely on user-defined networks, but they are not accessible from outside the Docker host. This is a security feature—containers are isolated from the outside world.

Port mapping creates a bridge between the host's network and the container's network. It forwards traffic from a port on the Docker host to a port inside the container. This allows external clients (browsers, API clients, other services) to connect to your containerized applications.

External Client │ ▼ Host Port: 8080 ──────┐ │ │ (Port Mapping) ▼ ▼ Docker Host Container Port: 80 (iptables forwarding)
Without port mapping, your container's web server on port 80 is only accessible from within Docker's internal network. Port mapping exposes it to the outside world.
The EXPOSE Instruction (Documentation Only)

The EXPOSE instruction in a Dockerfile is documentation. It tells users which ports the container listens on, but it does NOT actually publish the port. Publishing still requires the -p or -P flag when running the container.

EXPOSE is useful for: documenting container requirements, being used by -P (random port mapping), and being read by tools like Docker Compose. However, it has no effect on networking—containers can still communicate without EXPOSE, and ports are not opened on the host.

# Dockerfile with EXPOSE FROM nginx:alpine EXPOSE 80 EXPOSE 443/tcp # EXPOSE is just documentation - doesn't actually open ports! # Build the image docker build -t mynginx . # Run with EXPOSE but no -p - NOT accessible from host docker run -d --name nginx1 mynginx # Cannot access from host browser - no port mapping! # Run with -p to actually publish docker run -d --name nginx2 -p 8080:80 mynginx # Now accessible at http://localhost:8080 # Run with -P to publish all EXPOSED ports to random host ports docker run -d --name nginx3 -P mynginx docker port nginx3 # 80/tcp -> 0.0.0.0:32768 (random port)
Publishing Ports: The -p Flag

The -p (or --publish) flag maps a host port to a container port. The full syntax is: -p [host_ip:]host_port:container_port[/protocol]. You can use multiple -p flags to publish multiple ports.

Common patterns:

  • -p 8080:80 - Map host port 8080 to container port 80 (TCP default)
  • -p 127.0.0.1:8080:80 - Bind only to localhost, not accessible from other machines
  • -p 8080-8085:80-85 - Map a range of ports
  • -p 53:53/udp - Specify UDP protocol
  • -p 8080:80/tcp -p 8080:80/udp - Both TCP and UDP on same port
# Basic port mapping docker run -d --name web -p 8080:80 nginx # Bind to specific host IP (only localhost) docker run -d --name web-local -p 127.0.0.1:8080:80 nginx # Multiple ports docker run -d --name app \ -p 8080:3000 \ -p 5432:5432 \ -p 6379:6379 \ myapp # UDP port mapping (DNS) docker run -d --name dns -p 53:53/udp -p 53:53/tcp coredns/coredns # Port range mapping docker run -d --name streaming -p 8000-8010:8000-8010/udp mystreamer # Check published ports docker port web # 80/tcp -> 0.0.0.0:8080
Publish All Ports: The -P Flag

The -P (uppercase) flag publishes all ports that are EXPOSED in the Dockerfile to random high-numbered ports on the host. This is useful when you don't care which specific host ports are used, or when running multiple instances of the same container to avoid port conflicts.

To see which random ports were assigned, use docker port container-name. You can also configure the default port range (usually 32768-60999).

# Dockerfile with multiple EXPOSE FROM node:18-alpine EXPOSE 3000 EXPOSE 9229 # debug port EXPOSE 8080/udp # Run with -P (publish all exposed ports to random host ports) docker run -d --name myapp -P myapp # Check assigned ports docker port myapp # 3000/tcp -> 0.0.0.0:32768 # 9229/tcp -> 0.0.0.0:32769 # 8080/udp -> 0.0.0.0:32770 # Access application at http://localhost:32768 # Useful for running multiple instances without port conflicts for i in 1 2 3; do docker run -d --name app-$i -P myapp done
Host Networking Mode: No Port Mapping Needed

Host networking mode (--network host) removes network isolation entirely. The container uses the host's network stack directly—no port mapping required. The container's ports are automatically available on the host's IP address.

Host networking offers the highest performance (no NAT overhead) and is useful for performance-critical applications or when you need the container to bind to many ports. However, it reduces security and can cause port conflicts.

Important: Host network mode only works on Linux. On Docker Desktop (macOS/Windows), it behaves like the default bridge network.

# Run with host networking (Linux only) docker run -d --name web --network host nginx # Nginx is now accessible directly on host port 80 # No -p flag needed! http://localhost:80 works # Check that container uses host's network namespace docker exec web ip addr show # Shows same interfaces as running 'ip addr show' on host # Use case: performance-sensitive applications docker run -d --name metrics --network host prom/prometheus # Use case: binding to many ports docker run -d --name game-server --network host my-game-server
Host networking disables Docker's network isolation. Use it only when you understand the security implications and performance benefits justify it.
Port Conflicts: What Happens When Ports Collide?

When you try to map a host port that's already in use, Docker will fail to start the container. You'll see an error like "port is already allocated" or "bind: address already in use". Here's how to handle port conflicts:

  • Stop the container using that port, or
  • Choose a different host port, or
  • Use -P to let Docker pick a random port, or
  • Use host networking mode (if appropriate)
# This will fail if port 80 is already in use docker run -d --name web -p 80:80 nginx # Error: driver failed programming external connectivity... # Check what's using a port sudo lsof -i :80 # Linux/macOS netstat -ano | findstr :80 # Windows # Stop the conflicting container docker stop conflicting-container # Or use a different host port docker run -d --name web -p 8080:80 nginx # Or let Docker pick a random port docker run -d --name web -P nginx # Check what port was assigned docker port web
Viewing Published Ports
# List all port mappings for a container docker port myapp # 80/tcp -> 0.0.0.0:8080 # 443/tcp -> 0.0.0.0:8443 # Filter by container port docker port myapp 80 # 0.0.0.0:8080 # Filter by protocol docker port myapp 53/udp # 0.0.0.0:32768 # List all containers with their ports docker ps --format "table {{.Names}}\t{{.Ports}}" # Show all published ports across all containers docker ps --format "{{.Ports}}" | grep -v "^$"
Port Mapping in Docker Compose

In Docker Compose, port mapping is defined in the `ports` section. The syntax is similar to the `-p` flag. Docker Compose also supports the `expose` directive (documentation only, similar to EXPOSE in Dockerfile).

# docker-compose.yml version: '3.8' services: web: image: nginx ports: - "8080:80" # host:container - "127.0.0.1:8443:443" # bind to specific IP - "3000-3005:3000-3005" # port range - "53:53/udp" # UDP protocol expose: - "80" - "443" app: image: myapp ports: - "8080:3000" # expose without publishing (internal only) expose: - "3000"
Common Use Cases & Examples

Web Application: Expose web server to the world

docker run -d --name web -p 80:80 -p 443:443 nginx

Database (not recommended to expose directly): For development only

docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret postgres:15

Multiple Services in One Container: Map multiple ports

docker run -d --name app \ -p 8080:3000 \ -p 9229:9229 \ -p 5432:5432 \ myapp

Development with Hot Reload: Expose debug port

docker run -d --name dev \ -p 3000:3000 \ -p 9229:9229 \ -v "$PWD":/app \ node:18 npm run dev
Best Practices for Port Mapping
  • Use EXPOSE in Dockerfiles for documentation - It helps users know which ports to publish.
  • Don't expose databases directly to the host - In production, databases should only be accessible to other containers on internal networks.
  • Bind to localhost for development - Use -p 127.0.0.1:8080:80 to prevent external access during development.
  • Use distinct host ports for multiple containers - Avoid conflicts by using different host ports or -P.
  • Prefer specific host ports for production - Random ports (-P) are hard to configure for load balancers and DNS.
  • Use host networking sparingly - It disables Docker's network isolation. Only use when performance is critical.
  • Document port mappings in docker-compose.yml - This makes it clear how services are exposed.
Frequently Asked Questions
What's the difference between EXPOSE and -p?
EXPOSE is documentation - it doesn't publish anything. -p actually creates the port mapping. EXPOSE tells users "this container listens on port 80", but you still need -p to make it accessible from outside the host.
Can two containers use the same host port?
No, two containers cannot bind to the same host port simultaneously. The second container will fail to start with a "port already in use" error. Use different host ports or -P for random ports.
How do I map a port to a different host IP?
Use -p IP:host_port:container_port. For example, -p 192.168.1.100:8080:80 binds only to that specific IP address on the host.
Does port mapping work with UDP?
Yes! Use -p 53:53/udp for UDP, or -p 8080:80/tcp for TCP (default). You can also map both protocols: -p 53:53/tcp -p 53:53/udp.
Why can't I access my container on localhost:8080?
Check: Is the container running? docker ps. Did you use -p 8080:80 correctly? Is the container's application actually listening on port 80? Check with docker logs container.
Can I map a port after a container is running?
No, port mappings are set when the container is created. You cannot add or remove port mappings on a running container. You need to stop, remove, and recreate with the new port mapping.
What's the default port range for -P?
By default, Docker assigns random ports in the range 32768-60999. You can configure this by editing /etc/docker/daemon.json and setting userland-proxy and ip settings.
Does host networking work on Docker Desktop (macOS/Windows)?
No, host networking only works on Linux. On Docker Desktop, --network host behaves like the default bridge network. Use standard port mapping instead.
Previous: Container Communication Next: Docker Volumes Guide

Port mapping is how your containers connect to the outside world. Use -p for specific ports, -P for random ports, and reserve host networking for special cases.