Skip to content
beginner Phase 2 · Docker Networking

Port Mapping & Exposure

Map container ports to host and configure port publishing.

45m
0 problems
Topic Progress 0%

Port Mapping Fundamentals

Port Mapping Fundamentals

Port mapping exposes container ports to the host, allowing external clients to reach services running inside containers.

EXPOSE vs -p

# Dockerfile: EXPOSE documents which ports the container uses
EXPOSE 80
EXPOSE 443
# EXPOSE does NOT publish ports - it's purely documentation
# -p flag actually publishes ports to the host
docker run -d -p 8080:80 nginx  # host:8080 → container:80

Port Mapping Syntax

# Map host port 8080 to container port 80
docker run -d -p 8080:80 nginx

# Map to a specific host IP
docker run -d -p 127.0.0.1:8080:80 nginx

# Map random host port
docker run -d -p 80 nginx
# Docker picks a random port (e.g., 49153)

# Check the mapping
docker port web
# 80/tcp -> 0.0.0.0:49153

# Map multiple ports
docker run -d -p 80:80 -p 443:443 nginx

# Publish all EXPOSEd ports
# (maps each to a random host port)
docker run -d -P nginx

TCP vs UDP

# TCP (default)
docker run -d -p 53:53/udp mydns  # UDP only
docker run -d -p 53:53/tcp mydns  # TCP only

# Both TCP and UDP
docker run -d -p 53:53/udp -p 53:53/tcp mydns

Port Mapping Examples

# Web server
docker run -d --name web -p 80:80 -p 443:443 nginx:1.25

# Node.js application
docker run -d --name app -p 3000:3000 mynodeapp

# Database (accessible only from localhost)
docker run -d --name db -p 127.0.0.1:5432:5432 postgres:16

# Multiple services
docker run -d --name fullapp \
  -p 80:80 -p 443:443 -p 3000:3000 -p 5432:5432 \
  myfullapp

How Port Mapping Works Internally

How Port Mapping Works Internally

When you run docker run -p 8080:80 nginx, Docker configures several networking layers:

iptables Rules

Docker automatically creates iptables rules to forward traffic:

# View the DNAT rule for port mapping
sudo iptables -t nat -L DOCKER -n

# Example output:
# DNAT  tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:8080 to:172.18.0.2:80

# View the FORWARD rule
sudo iptables -L DOCKER -n
# ACCEPT  tcp  --  0.0.0.0/0  172.18.0.2  tcp dpt:80

Traffic Flow

Client (host:8080)
     │
     ▼
iptables DNAT (PREROUTING)
     │
     ▼
FORWARD chain (filter table)
     │
     ▼
Container (172.18.0.2:80)

Docker Proxy

For some configurations, Docker runs a user-space proxy (docker-proxy) to handle port forwarding:

# Check docker-proxy process
ps aux | grep docker-proxy
# docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 
#   -container-ip 172.18.0.2 -container-port 80

Docker uses iptables when possible (more efficient) but falls back to docker-proxy for:

  • Host network mode
  • Specific platform configurations
  • When iptables is disabled

Port Conflict Resolution

# Error: port is already allocated
docker run -d -p 8080:80 nginx
# Error: Bind for 0.0.0.0:8080 failed: port is already allocated

# Find what's using the port
sudo lsof -i :8080
# or
sudo ss -tlnp | grep 8080

# Solutions:
# 1. Use a different host port
docker run -d -p 8081:80 nginx

# 2. Stop the existing container
docker stop old-nginx && docker rm old-nginx

# 3. Use --use-port (Docker 20.10+)
# Releases port when container stops
docker run -d -p 8080:80 --name web nginx

localhost Binding

# Bind to all interfaces (default)
docker run -d -p 8080:80 nginx  # Accessible from network

# Bind to localhost only
docker run -d -p 127.0.0.1:8080:80 nginx  # Only from host

# Use case: database should not be network-accessible
docker run -d -p 127.0.0.1:5432:5432 postgres:16

Troubleshooting Port Issues

Troubleshooting Port Issues

Common Issues

1. Port already allocated:

# Diagnose
sudo lsof -i :8080
sudo netstat -tlnp | grep 8080

# Find and stop the container using the port
docker ps --format "{{.Names}} {{.Ports}}" | grep 8080
docker stop <container-name>

2. Connection refused:

# Check if container is running
docker ps

# Check if application is listening inside container
docker exec web ss -tlnp
# If not listening, the app hasn't started or is binding to wrong interface

# Check if app binds to 0.0.0.0 (all interfaces)
docker exec web netstat -tlnp
# Should show 0.0.0.0:80, not 127.0.0.1:80

3. Connection timeout:

# Check iptables rules
sudo iptables -t nat -L -n | grep 8080

# Check if Docker proxy is running
ps aux | grep docker-proxy

# Check firewall
sudo ufw status
sudo firewall-cmd --list-all

4. Can't access from another machine:

# Verify port binding
docker port web
# 80/tcp -> 0.0.0.0:8080

# Check host firewall
sudo iptables -L INPUT -n

# If bound to 127.0.0.1, it's not accessible remotely
docker port web
# 80/tcp -> 127.0.0.1:8080  # ← This won't work remotely

Debugging Workflow

# Step 1: Container status
docker ps -a | grep web

# Step 2: Container logs
docker logs web --tail 50

# Step 3: Port mappings
docker port web

# Step 4: Network settings
docker inspect --format '{{json .NetworkSettings.Ports}}' web

# Step 5: Test from inside
docker exec web curl -s http://localhost:80

# Step 6: Test from host
curl -s http://localhost:8080

# Step 7: Check host firewall
sudo iptables -L -n

Useful Commands

# List all published ports
docker ps --format "table {{.Names}}\t{{.Ports}}"

# Find container by port
docker ps --filter "publish=8080"

# See all port mappings
docker inspect --format '{{range $k, $v := .NetworkSettings.Ports}}{{$k}} -> {{range $v}}{{.HostIp}}:{{.HostPort}}{{end}}{{println}}{{end}}' $(docker ps -q)