Port Mapping and Exposure
Port mapping allows external access to container services. Understanding how ports work in Docker is essential for deploying accessible applications.
EXPOSE vs Publishing
Two distinct concepts:
| Concept | Purpose | Effect |
|---|---|---|
| EXPOSE | Documentation | None - just metadata |
| Publish (-p) | Runtime mapping | Actually maps ports |
# In Dockerfile - documents intent, no effect on its own
EXPOSE 3000
# At runtime - actually maps the port
docker run -p 3000:3000 myapp
Publishing Ports
Basic Port Mapping
# Map host port 8080 to container port 80
docker run -p 8080:80 nginx
# Format: HOST_PORT:CONTAINER_PORT
# ▲ ▲
# │ └── Port inside container
# └── Port on host machine
Multiple Port Mappings
# Map multiple ports
docker run -p 80:80 -p 443:443 nginx
# Or with different host ports
docker run -p 8080:80 -p 8443:443 nginx
Random Host Port
# Let Docker choose available host port
docker run -P nginx # Uppercase P - publish all exposed ports
# Or specific container port, random host port
docker run -p 80 nginx
# Find assigned port
docker port containername
# 80/tcp -> 0.0.0.0:49153
Port Binding Options
Bind to Specific Interface
# Bind to all interfaces (default)
docker run -p 80:80 nginx
docker run -p 0.0.0.0:80:80 nginx
# Bind to localhost only
docker run -p 127.0.0.1:80:80 nginx
# Bind to specific IP
docker run -p 192.168.1.100:80:80 nginx
Security Consideration
# Publicly accessible (default)
docker run -p 80:80 nginx
# Anyone on network can access
# Only local access
docker run -p 127.0.0.1:80:80 nginx
# Only accessible from host machine
TCP and UDP Ports
# TCP (default)
docker run -p 80:80 nginx
docker run -p 80:80/tcp nginx
# UDP
docker run -p 53:53/udp dns-server
# Both TCP and UDP
docker run -p 53:53/tcp -p 53:53/udp dns-server
Viewing Port Mappings
# List mapped ports for a container
docker port mycontainer
# Output:
80/tcp -> 0.0.0.0:8080
443/tcp -> 0.0.0.0:8443
# Specific port
docker port mycontainer 80
# 0.0.0.0:8080
# From container inspect
docker inspect -f '{{json .NetworkSettings.Ports}}' mycontainer
Port Mapping in docker run
Long-form Options
# Using --publish
docker run --publish 8080:80 nginx
# Using --publish-all
docker run --publish-all nginx
Common Patterns
# Web server
docker run -d -p 80:80 -p 443:443 nginx
# Database
docker run -d -p 5432:5432 postgres
# Development server
docker run -d -p 3000:3000 node-app
# Multiple services
docker run -d \
-p 8080:8080 \
-p 9090:9090 \
-p 9091:9091 \
prometheus
Port Conflicts
Finding Conflicting Ports
# Check what's using a port (Linux/Mac)
lsof -i :8080
netstat -tlnp | grep 8080
# Check what's using a port (Windows)
netstat -ano | findstr :8080
Resolving Conflicts
# Port already in use
docker run -p 80:80 nginx
# Error: port is already allocated
# Solution 1: Use different host port
docker run -p 8080:80 nginx
# Solution 2: Stop conflicting process
sudo kill $(lsof -t -i:80)
# Solution 3: Let Docker choose
docker run -p 80 nginx
Container-to-Container Communication
Containers on the same network communicate via container ports, not published ports:
# Create network
docker network create myapp
# Database - no need to publish port
docker run -d --name db --network myapp postgres
# API connects via container port 5432
docker run -d \
--name api \
--network myapp \
-e DB_HOST=db \
-e DB_PORT=5432 \
myapi
# Only publish what needs external access
docker run -d \
--name web \
--network myapp \
-p 80:80 \
nginx
┌─────────────────────────────────────────────────────────────┐
│ Docker Host │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ myapp network │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ DB │◄───│ API │◄───│ Web │ │ │
│ │ │ :5432 │ │ :3000 │ │ :80 │◄──┐ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
│ │ (internal) (internal) (published) │ │ │
│ └─────────────────────────────────────────────────│──────┘ │
│ │ │
│ │ │
└────────────────────────────────────────────────────│────────┘
│
Host port 80
│
┌─────▼─────┐
│ External │
│ Traffic │
└───────────┘
Development Port Mappings
Full-Stack Development Setup
# Frontend
docker run -d \
--name frontend \
-p 3000:3000 \
-v $(pwd)/frontend:/app \
react-app
# Backend API
docker run -d \
--name backend \
-p 4000:4000 \
-v $(pwd)/backend:/app \
node-api
# Database
docker run -d \
--name db \
-p 5432:5432 \
postgres
Port Allocation Strategy
| Service | Development | Production |
|---|---|---|
| Frontend | 3000:3000 | 80:80 |
| API | 4000:4000 | 8080:8080 |
| Database | 5432:5432 | Not published |
| Redis | 6379:6379 | Not published |
EXPOSE in Dockerfile
The EXPOSE instruction documents intended ports:
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
# Document that app uses port 3000
EXPOSE 3000
CMD ["npm", "start"]
Using EXPOSE
# EXPOSE alone doesn't publish ports
docker run myapp
# Port 3000 is NOT accessible externally
# Use -P to publish all EXPOSE'd ports
docker run -P myapp
# Port 3000 mapped to random host port
# Or explicitly publish
docker run -p 3000:3000 myapp
Checking Published Ports
# Using docker ps
docker ps
# PORTS NAMES
# 0.0.0.0:8080->80/tcp web
# Using docker port
docker port web
# 80/tcp -> 0.0.0.0:8080
# Using docker inspect
docker inspect -f '{{range $p, $conf := .NetworkSettings.Ports}}{{$p}} -> {{(index $conf 0).HostPort}}{{"\n"}}{{end}}' web
Key Takeaways
- EXPOSE documents ports but doesn't publish them
- Use
-p HOST:CONTAINERto publish ports - Bind to 127.0.0.1 for local-only access
- Containers on the same network don't need published ports to communicate
- Use
-Pto publish all EXPOSE'd ports to random host ports - Only publish ports that need external access
- Consider security when choosing which interfaces to bind
- Check for port conflicts before running containers

