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)