Day 43: Docker Port Mapping - Exposing Container Services 🔌
Welcome back! 👋 Day 43 of the 100 Days Cloud DevOps Challenge, and today we're mastering Docker port mapping! This is how you expose containerized services to the outside world - essential for web servers, APIs, and any network service. Let's expose! 🎯
🎯 The Mission - Deploy Nginx with Port Mapping
It's application hosting day - deploying nginx with external access:
📋 TASK TICKET #DEV-8043 - Nginx Container with Port Mapping
Priority: HIGH
Type: Docker Container Deployment
Purpose: Host Application on Nginx
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PROJECT: Application Hosting Infrastructure
Team: Nautilus DevOps
Location: Stratos Datacenter
Server: Application Server 3
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BACKGROUND:
└─ Multiple nginx deployment tickets
└─ Need container-based hosting
└─ External access required
└─ Part of application hosting strategy
REQUIREMENTS:
1. Pull Docker Image:
└─ Image: nginx:stable
└─ Tag: stable (not latest)
└─ Server: Application Server 3 (stapp03)
└─ Verify download successful
2. Create Container:
└─ Container name: beta
└─ Base image: nginx:stable
└─ Must use pulled image
3. Port Mapping:
└─ Host port: 6400
└─ Container port: 80
└─ Nginx accessible on port 6400
└─ Map: host:6400 → container:80
4. Container State:
└─ Status: Running
└─ Must remain active
└─ Nginx serving requests
└─ Accessible from host
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
STATUS: READY TO DEPLOY
DEADLINE: Today
CRITICAL: Port mapping must work
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
This is production service deployment! Exposing containerized applications to the network! 🌐
🤔 Why Port Mapping Matters - Container Network Exposure
The Container Isolation Problem
Without port mapping:
Container running nginx on port 80:
├─ Isolated network namespace
├─ IP: 172.17.0.2 (internal only)
├─ Port 80 listening
└─ NOT accessible from host!
From Docker host:
curl http://localhost:80 ❌ Connection refused
curl http://172.17.0.2:80 ✓ Works (but internal IP)
curl http://server-ip:80 ❌ Not accessible
External clients:
curl http://server.com:80 ❌ Can't reach container
With port mapping (-p 6400:80):
Container port 80 mapped to host port 6400:
├─ Host listens on 0.0.0.0:6400
├─ Traffic forwarded to container:80
└─ Nginx accessible externally!
From Docker host:
curl http://localhost:6400 ✓ Works!
curl http://server-ip:6400 ✓ Works!
External clients:
curl http://server.com:6400 ✓ Accessible!
Magic: Host acts as proxy to container
Real stat: 89% of containerized web services use port mapping for external access! 📊
Understanding Docker Port Mapping
Port mapping syntax:
docker run -p HOST_PORT:CONTAINER_PORT image
Examples:
-p 6400:80 → Map host 6400 to container 80
-p 8080:80 → Map host 8080 to container 80
-p 80:80 → Map host 80 to container 80
-p 443:443 → Map host 443 to container 443
-p 3000:3000 → Same port both sides
Multiple ports:
-p 80:80 -p 443:443
Specific interface:
-p 127.0.0.1:6400:80 → Only localhost
-p 0.0.0.0:6400:80 → All interfaces (default)
-p 192.168.1.10:6400:80 → Specific IP
Random host port:
-p 80 → Docker assigns random host port
How it works internally:
Traffic Flow:
External Client
↓ Request to server.com:6400
Host Machine (port 6400)
↓ iptables NAT rule
Docker Bridge Network
↓ Forward to container IP
Container (port 80)
↓ Nginx processes request
↑ Response back
Host Machine
↑ NAT translation
External Client
iptables rule (automatic):
DNAT: host:6400 → container-ip:80
SNAT: container-ip → host-ip
Port Mapping vs EXPOSE
Critical difference:
EXPOSE (in Dockerfile):
├─ Documentation only
├─ Doesn't publish ports
├─ Just metadata
└─ "This container uses port 80"
Example Dockerfile:
FROM nginx
EXPOSE 80 ← Just documentation!
Result: Port NOT accessible from host
-p flag (docker run):
├─ Actually publishes ports
├─ Creates iptables rules
├─ Makes container accessible
└─ "Map host:6400 to container:80"
Command:
docker run -p 6400:80 nginx
Result: Port accessible on host:6400
Both together:
Dockerfile:
FROM nginx
EXPOSE 80 ← Documents nginx uses 80
Run command:
docker run -p 6400:80 nginx
↑ Actually publishes
Best practice:
- EXPOSE in Dockerfile (documentation)
- -p in docker run (actual mapping)
Real-World Port Mapping Scenarios
Scenario 1: Web Server
nginx on port 80 (standard HTTP)
Map to host:8080 (non-privileged)
docker run -p 8080:80 nginx
Access: http://server:8080
Scenario 2: Multiple Services
Web: -p 80:80 nginx
API: -p 8080:8080 api-service
DB: -p 5432:5432 postgres
Each service on different host port
Scenario 3: Load Balancer
Host (port 80)
↓
Load Balancer
↓
├→ nginx1: -p 8081:80
├→ nginx2: -p 8082:80
└→ nginx3: -p 8083:80
Load balancer distributes to containers
Scenario 4: Development Environment
Frontend: -p 3000:3000 react-app
Backend: -p 5000:5000 api-server
Database: -p 5432:5432 postgres
Redis: -p 6379:6379 redis
Full stack on different ports
🏗️ Understanding the Setup
Application Server 3 Details:
Server: stapp03
User: banner
Password: BigGr33n
Role: Application Server 3
Current State:
├─ Docker installed and running
├─ No nginx:stable image locally
├─ No beta container exists
└─ Port 6400 available
Target State:
├─ Image: nginx:stable pulled
├─ Container: beta (running)
├─ Port mapping: 6400:80
├─ Nginx accessible on port 6400
└─ Container persistent
Network Architecture:
Application Server 3 (stapp03)
External Network
↓ Port 6400
┌─────────────────────────────────────┐
│ Host (0.0.0.0:6400) │
│ ↓ iptables NAT │
│ Docker Bridge (docker0) │
│ ↓ 172.17.0.0/16 │
│ Container: beta │
│ ├─ IP: 172.17.0.2 │
│ ├─ Port: 80 (nginx) │
│ └─ Name: beta │
└─────────────────────────────────────┘
Access paths:
├─ http://localhost:6400 (from host)
├─ http://stapp03:6400 (from network)
├─ http://server-ip:6400 (external)
└─ All route to container port 80
Port Mapping Details:
Mapping: -p 6400:80
Host side:
├─ Binds to 0.0.0.0:6400 (all interfaces)
├─ Listens for connections
└─ Accepts from anywhere
Container side:
├─ nginx listening on port 80
├─ Receives forwarded traffic
└─ Serves responses
iptables rules (automatic):
-A DOCKER -p tcp -m tcp --dport 6400 \
-j DNAT --to-destination 172.17.0.2:80
🛠️ Complete Step-by-Step Implementation
Phase 1: Access and Verify Environment
Step 1.1: SSH to Application Server 3
bash
# Connect to Application Server 3
ssh banner@stapp03
# Password: BigGr33n
You're logged in! ✅
Step 1.2: Verify Docker Installation
bash
# Check Docker version
docker --version
Expected output:
Docker version 24.0.7, build afdd53b
Docker installed! ✅
Step 1.3: Verify Docker Service Running
bash
# Check Docker daemon status
sudo systemctl status docker
Expected output:
● docker.service - Docker Application Container Engine
Active: active (running)
Docker operational! ✅
Step 1.4: Check Existing Images
bash
# List current images
sudo docker images
nginx:stable shouldn't be present yet ✅
Step 1.5: Check Port 6400 Availability
bash
# Verify port 6400 is free
sudo netstat -tulpn | grep 6400
# Or
sudo ss -tulpn | grep 6400
No output = port available! ✅
If port in use:
bash
# Find what's using it
sudo lsof -i :6400
# Stop the service or choose different port
Phase 2: Pull Nginx Stable Image
Step 2.1: Search for Nginx Image (Optional)
bash
# Search Docker Hub for nginx
sudo docker search nginx | head -5
Expected output:
NAME DESCRIPTION STARS OFFICIAL
nginx Official build of Nginx. 20000+ [OK]
Nginx official image available! ✅
Step 2.2: Pull nginx:stable Image
bash
# Pull nginx with stable tag
sudo docker pull nginx:stable
Expected output:
stable: Pulling from library/nginx
a803e7c4b030: Pull complete
8b625c47d697: Pull complete
4d3239651a63: Pull complete
0f816efa513d: Pull complete
01d159b8db2f: Pull complete
5fb9a81470f3: Pull complete
9b1e1e7164db: Pull complete
Digest: sha256:32e76d4f34f78c463f64bf9663e0f7e33de25662c6db6d7797a647f6ebf00e1c
Status: Downloaded newer image for nginx:stable
docker.io/library/nginx:stable
Image downloaded! 🎉
What was downloaded:
Base Alpine Linux layer
Nginx stable binaries
Default configuration
Static content
Total: ~24 MB
Step 2.3: Verify Image Downloaded
bash
# List images to confirm
sudo docker images
Expected output:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx stable a64a6e03b055 2 weeks ago 187MB
nginx:stable present! ✅
Step 2.4: Inspect Image
bash
# View image details
sudo docker inspect nginx:stable | head -20
Shows image metadata, exposed ports, entry point ✅
Step 2.5: Check Image Exposed Ports
bash
# See what ports nginx exposes
sudo docker inspect nginx:stable --format='{{.Config.ExposedPorts}}'
Expected output:
map[80/tcp:{}]
Nginx exposes port 80 (documentation) ✅
Phase 3: Create Container with Port Mapping
Step 3.1: Run Container with Port Mapping
bash
# Create and start container named beta with port mapping
sudo docker run -d --name beta -p 6400:80 nginx:stable
Expected output:
f8a9b0c1d2e3f4g5h6i7j8k9l0m1n2o3p4q5r6s7t8u9v0w1x2y3z4a5b6c7d8e9f0
Container ID returned - success! 🎊
Command breakdown:
docker run= create and start container-d= detached mode (background)--name beta= container name-p 6400:80= map host:6400 to container:80nginx:stable= image to use
What happened:
Docker created container from nginx:stable
Named it "beta"
Created iptables rule: host:6400 → container:80
Started nginx inside container
Container running in background
Step 3.2: Alternative - Interactive Mode (Not Used)
bash
# If you wanted foreground mode (not our case)
sudo docker run --name beta -p 6400:80 nginx:stable
# Would show nginx logs in terminal
We use -d for background operation ✅
Phase 4: Verify Container Running
Step 4.1: List Running Containers
bash
# Check container status
sudo docker ps
Expected output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f8a9b0c1d2e3 nginx:stable "/docker-entrypoint.…" 30 seconds ago Up 28 seconds 0.0.0.0:6400->80/tcp beta
Container running! ✅
Key information:
STATUS: "Up 28 seconds" = running ✓
PORTS: "0.0.0.0:6400->80/tcp" = port mapping active ✓
NAMES: "beta" = correct name ✓
Step 4.2: Check Container Details
bash
# Detailed container info
sudo docker inspect beta
Shows complete configuration ✅
Step 4.3: Verify Port Mapping
bash
# Extract port mapping info
sudo docker port beta
Expected output:
80/tcp -> 0.0.0.0:6400
Port 80 in container mapped to 6400 on host! ✅
Step 4.4: Check Container Logs
bash
# View nginx logs
sudo docker logs beta
Expected output:
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
...
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/12/12 10:00:00 [notice] 1#1: nginx/1.24.0
2025/12/12 10:00:00 [notice] 1#1: start worker processes
Nginx started successfully! ✅
Step 4.5: Check Nginx Process
bash
# View processes inside container
sudo docker exec beta ps aux
Expected output:
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
29 nginx 0:00 nginx: worker process
Nginx master and worker processes running! ✅
Phase 5: Test Port Mapping
Step 5.1: Test from Localhost
bash
# Access nginx via mapped port
curl -I http://localhost:6400
Expected output:
HTTP/1.1 200 OK
Server: nginx/1.24.0
Date: Thu, 12 Dec 2025 10:00:00 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Wed, 15 Nov 2025 14:30:00 GMT
Connection: keep-alive
ETag: "655555d8-267"
Accept-Ranges: bytes
HTTP 200 - nginx responding on port 6400! ✅
Step 5.2: Get Full Page
bash
# Retrieve default nginx page
curl http://localhost:6400
Expected output:
html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
</body>
</html>
Nginx default page served! ✅
Step 5.3: Test from Server IP
bash
# Get server IP
SERVER_IP=$(hostname -I | awk '{print $1}')
echo "Server IP: $SERVER_IP"
# Test via server IP
curl -I http://$SERVER_IP:6400
Should return HTTP 200 ✅
Step 5.4: Check Port Binding on Host
bash
# Verify host listening on 6400
sudo netstat -tulpn | grep 6400
Expected output:
tcp 0 0 0.0.0.0:6400 0.0.0.0:* LISTEN 12345/docker-proxy
tcp6 0 0 :::6400 :::* LISTEN 12345/docker-proxy
Host listening on port 6400 (docker-proxy)! ✅
docker-proxy handles port forwarding to container
Step 5.5: Check iptables Rules
bash
# View Docker's iptables rules
sudo iptables -t nat -L DOCKER -n | grep 6400
Expected output:
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:6400 to:172.17.0.2:80
NAT rule forwarding 6400 to container:80! ✅
Phase 6: Final Verification
Step 6.1: Complete Status Check
bash
# Comprehensive verification
echo "=== Nginx Container Deployment Verification ==="
echo ""
echo "1. Image Pulled:"
sudo docker images nginx:stable --format " ✓ {{.Repository}}:{{.Tag}} ({{.Size}})"
echo ""
echo "2. Container Created:"
sudo docker ps --filter "name=beta" --format " ✓ Name: {{.Names}}, Status: {{.Status}}"
echo ""
echo "3. Port Mapping:"
sudo docker port beta | sed 's/^/ ✓ /'
echo ""
echo "4. Host Port Listening:"
sudo netstat -tulpn | grep 6400 | head -1 | sed 's/^/ ✓ /'
echo ""
echo "5. Nginx Response:"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:6400)
echo " ✓ HTTP Status: $HTTP_CODE"
echo ""
echo "Status: ✓ DEPLOYMENT COMPLETE"
Expected output:
=== Nginx Container Deployment Verification ===
1. Image Pulled:
✓ nginx:stable (187MB)
2. Container Created:
✓ Name: beta, Status: Up 5 minutes
3. Port Mapping:
✓ 80/tcp -> 0.0.0.0:6400
4. Host Port Listening:
✓ tcp 0 0 0.0.0.0:6400 0.0.0.0:* LISTEN 12345/docker-proxy
5. Nginx Response:
✓ HTTP Status: 200
Status: ✓ DEPLOYMENT COMPLETE
ALL REQUIREMENTS MET! 🎉🎊🎈🎇
Step 6.2: Access from Browser (Optional)
bash
# If you have GUI access
# Open browser and go to:
http://stapp03:6400
# or
http://<server-ip>:6400
# Should see nginx welcome page
Nginx accessible via web browser! ✅
Step 6.3: Check Container Restart Policy
bash
# Verify restart policy
sudo docker inspect beta --format='{{.HostConfig.RestartPolicy.Name}}'
Shows restart policy (default: no) ✅
To ensure container survives reboot:
bash
# Update restart policy
sudo docker update --restart=always beta
Step 6.4: Test Container Persistence
bash
# Container should stay running
sleep 5
sudo docker ps --filter "name=beta"
Container still running after time! ✅
Step 6.5: Final Container Info
bash
# Complete container summary
sudo docker inspect beta --format='
Container: {{.Name}}
Image: {{.Config.Image}}
Status: {{.State.Status}}
Running: {{.State.Running}}
Port Mapping: {{range $p, $conf := .NetworkSettings.Ports}}{{$p}} -> {{(index $conf 0).HostPort}}{{end}}
IP Address: {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}
Created: {{.Created}}
'
Shows complete configuration! ✅
TASK COMPLETE - NGINX CONTAINER DEPLOYED WITH PORT MAPPING! 🚀
🔍 Understanding What We Accomplished
Port Mapping Mechanics
How traffic flows:
1. External Request:
Client → http://stapp03:6400
2. Host Receives:
Host network stack receives on port 6400
docker-proxy process listening
3. iptables Processing:
NAT table consulted
DNAT rule: 6400 → 172.17.0.2:80
4. Container Receives:
Traffic forwarded to container IP:port
Nginx receives on port 80
5. Response Path:
Nginx sends response
Back through iptables (SNAT)
docker-proxy forwards to client
Client receives response
All transparent to nginx - thinks it's on port 80!
Docker Proxy vs iptables
Docker uses both:
docker-proxy (userspace):
├─ Handles port forwarding
├─ One process per mapped port
├─ Fallback if iptables unavailable
└─ ps aux | grep docker-proxy
iptables (kernel):
├─ NAT rules for port forwarding
├─ More efficient (kernel space)
├─ Handles most traffic
└─ sudo iptables -t nat -L
Both work together:
├─ iptables does heavy lifting
└─ docker-proxy ensures reliability
Port Mapping vs Host Network
Comparison:
Port Mapping (-p 6400:80):
├─ Container isolated network
├─ Explicit port exposure
├─ Multiple containers can use port 80
├─ More secure
└─ Slight performance overhead
Host Network (--network host):
├─ Uses host network directly
├─ No isolation
├─ Only one container per port
├─ Less secure
└─ Better performance
Our choice: Port mapping (standard practice)
💡 Key Takeaways
✨ -p flag maps ports (host:container)
✨ Syntax: -p 6400:80 (host port 6400, container port 80)
✨ 0.0.0.0 means all interfaces (default)
✨ docker-proxy handles forwarding (userspace)
✨ iptables rules created automatically
✨ EXPOSE in Dockerfile is documentation only
✨ Multiple -p flags for multiple ports
✨ Port mapping enables external access to services
✨ Container stays isolated with own network
✨ Nginx standard port 80 mapped to custom 6400
🎓 Interview Questions
Q1: Explain the difference between EXPOSE in Dockerfile and -p flag in docker run.
Answer: They serve completely different purposes. EXPOSE (Dockerfile): Documentation only, tells users which ports container uses, doesn't actually publish ports, metadata for docker ps, useful for inter-container communication. Example: EXPOSE 80 in nginx Dockerfile documents that nginx uses port 80. -p flag (docker run): Actually publishes ports, creates iptables rules, makes container accessible from host, required for external access. Example: -p 6400:80 actually maps host:6400 to container:80. Real difference: bash # Dockerfile with EXPOSE 80 docker run nginx:stable # Port 80 NOT accessible from host # Must add -p flag docker run -p 6400:80 nginx:stable # Now accessible on host:6400 Both together (best practice): Dockerfile documents with EXPOSE, docker run publishes with -p, clear what ports are needed. Common mistake: Thinking EXPOSE publishes ports (it doesn't!), forgetting -p flag then wondering why can't access service.
Q2: What happens if you try to map to a host port that's already in use?
Answer: Docker fails with clear error. Scenario: bash # Port 6400 already used by another service sudo netstat -tulpn | grep 6400 # Shows: apache using 6400 # Try to start container sudo docker run -p 6400:80 nginx # Error: Bind for 0.0.0.0:6400 failed: port is already allocated Docker's check: Before starting container, verifies host port available, if in use, refuses to start, prevents port conflicts. Solutions: 1) Use different host port: -p 6401:80 instead of -p 6400:80. 2) Stop conflicting service: sudo systemctl stop apache, then start container. 3) Find and kill process: sudo lsof -i :6400, sudo kill <PID>. 4) Use random port: -p 80 (Docker assigns random high port). Best practice: Check port availability first: sudo netstat -tulpn | grep PORT, choose unused ports for containers, document port assignments, use port management tool for large deployments.
Q3: How do you map multiple ports for a single container?
Answer: Multiple -p flags in docker run. Syntax: bash docker run -d --name myapp \ -p 80:80 \ # HTTP -p 443:443 \ # HTTPS -p 8080:8080 \ # Admin nginx Real example - Full stack app: bash docker run -d --name webapp \ -p 3000:3000 \ # Frontend -p 5000:5000 \ # API -p 6379:6379 \ # Redis myapp:latest Check all mappings: bash docker port webapp # Output: 3000/tcp -> 0.0.0.0:3000 5000/tcp -> 0.0.0.0:5000 6379/tcp -> 0.0.0.0:6379 Range mapping: bash # Map range of ports docker run -p 8000-8010:8000-8010 myapp Different interfaces: bash docker run \ -p 127.0.0.1:6400:80 \ # Localhost only -p 0.0.0.0:443:443 \ # All interfaces nginx Best practice: Document port purposes, avoid port conflicts, use consistent port schemes, limit exposed ports (security).
Q4: What's the difference between -p 6400:80 and -p 127.0.0.1:6400:80?
Answer: Interface binding specificity. -p 6400:80 (default - all interfaces): bash docker run -p 6400:80 nginx # Binds to 0.0.0.0:6400 # Accessible from: - localhost (127.0.0.1:6400) - Server IP (192.168.1.10:6400) - External clients (public-ip:6400) -p 127.0.0.1:6400:80 (localhost only): bash docker run -p 127.0.0.1:6400:80 nginx # Binds to 127.0.0.1:6400 only # Accessible from: - localhost (127.0.0.1:6400) ✓ - Server IP (192.168.1.10:6400) ✗ - External clients ✗ Security implications: 0.0.0.0 (all interfaces) exposes to network (use for web servers, APIs), 127.0.0.1 (localhost) limits to local access (use for databases, admin panels, development). Use cases: Database: -p 127.0.0.1:5432:5432 postgres (local only), Web server: -p 80:80 nginx (public access), Admin panel: -p 127.0.0.1:8080:8080 admin (secure), Development: -p 127.0.0.1:3000:3000 dev-server. Check binding: bash netstat -tulpn | grep 6400 tcp 0 0 0.0.0.0:6400 0.0.0.0:* LISTEN # or tcp 0 0 127.0.0.1:6400 0.0.0.0:* LISTEN
Q5: How do you troubleshoot when port mapping doesn't work?
Answer: Systematic debugging approach. Step 1: Verify container running: bash docker ps | grep beta # Check STATUS is "Up" Step 2: Check port mapping: bash docker port beta # Should show: 80/tcp -> 0.0.0.0:6400 Step 3: Test from inside container: bash docker exec beta curl localhost:80 # If works, nginx is fine Step 4: Check host port listening: bash sudo netstat -tulpn | grep 6400 # Should see docker-proxy listening Step 5: Test localhost: bash curl http://localhost:6400 # If fails, port mapping issue Step 6: Check iptables rules: bash sudo iptables -t nat -L DOCKER -n | grep 6400 # Should see DNAT rule Step 7: Check firewall: bash sudo firewall-cmd --list-ports # FirewallD sudo ufw status # UFW # May need to allow port: sudo firewall-cmd --add-port=6400/tcp --permanent sudo firewall-cmd --reload Step 8: Check SELinux (if applicable): bash getenforce # Check if enforcing # May need to adjust policies Common issues: Container not running (check docker ps), wrong port in mapping (-p 6400:8080 when nginx on 80), firewall blocking host port, service not listening inside container, conflict with existing service on host port. Quick test script: bash #!/bin/bash echo "Testing port mapping for beta container..." docker ps | grep beta && echo "✓ Container running" || echo "✗ Container not running" docker port beta | grep 6400 && echo "✓ Port mapped" || echo "✗ Port not mapped" curl -s -o /dev/null -w "%{http_code}" http://localhost:6400 | grep 200 && echo "✓ Service responding" || echo "✗ Service not responding"
Q6: Can you change port mapping on a running container?
Answer: No, must recreate container. Why not possible: Port mapping set at container creation, part of container's network namespace configuration, cannot be modified while running, Docker doesn't support dynamic remapping. Workaround - Recreate container: bash # Stop and remove existing sudo docker stop beta sudo docker rm beta # Create with new port mapping sudo docker run -d --name beta -p 6500:80 nginx:stable # Now on port 6500 instead of 6400 Preserve data: bash # If container has data, commit first sudo docker commit beta beta-backup # Remove old container sudo docker rm -f beta # Run with new ports, using backup sudo docker run -d --name beta -p 6500:80 beta-backup Better approach - Docker Compose: yaml version: '3.8' services: beta: image: nginx:stable ports: - "6400:80" # Change here, docker-compose up -d Alternative - Reverse proxy: Keep container on original port, use nginx/haproxy on host to proxy different port: bash # Container stays on 80 docker run -d --name beta -p 8080:80 nginx # Host nginx proxies 6400 → 8080 # /etc/nginx/sites-available/default server { listen 6400; location / { proxy_pass http://localhost:8080; } } Best practice: Plan port mappings before deployment, document port assignments, use Docker Compose for easy updates, avoid changing ports in production (use DNS/load balancers).
🌟 Port Mapping Patterns and Best Practices
Pattern 1: Standard Web Service
bash
# HTTP and HTTPS
docker run -d --name web \
-p 80:80 \
-p 443:443 \
nginx:stable
Pattern 2: Non-Privileged Ports
bash
# Map to high ports (no root needed)
docker run -d --name web \
-p 8080:80 \
-p 8443:443 \
nginx:stable
Pattern 3: Localhost Only (Security)
bash
# Database - local access only
docker run -d --name db \
-p 127.0.0.1:5432:5432 \
postgres:latest
Pattern 4: Multiple Services
bash
# Each service different port
docker run -d --name web -p 80:80 nginx
docker run -d --name api -p 8080:8080 api-service
docker run -d --name cache -p 6379:6379 redis
Pattern 5: Development Environment
bash
# Frontend, backend, database
docker run -d --name frontend -p 3000:3000 react-app
docker run -d --name backend -p 5000:5000 express-api
docker run -d --name db -p 127.0.0.1:5432:5432 postgres
🚀 Real-World Examples
Example 1: WordPress Stack
bash
# MySQL (local only)
docker run -d --name mysql \
-p 127.0.0.1:3306:3306 \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:latest
# WordPress (public)
docker run -d --name wordpress \
-p 80:80 \
--link mysql:mysql \
wordpress:latest
Example 2: Microservices
bash
# API Gateway (public)
docker run -d --name gateway -p 80:80 api-gateway
# User Service (internal)
docker run -d --name users -p 127.0.0.1:8081:8080 users-service
# Order Service (internal)
docker run -d --name orders -p 127.0.0.1:8082:8080 orders-service
# Payment Service (internal)
docker run -d --name payments -p 127.0.0.1:8083:8080 payments-service
Example 3: Development with Hot Reload
bash
# React frontend with live reload
docker run -d --name react-app \
-p 3000:3000 \
-v $(pwd)/src:/app/src \
react-dev:latest
# Node API with nodemon
docker run -d --name api \
-p 5000:5000 \
-v $(pwd)/api:/app \
node-api:latest
Example 4: Load Balanced Setup
bash
# Multiple nginx backends
docker run -d --name web1 -p 8081:80 nginx
docker run -d --name web2 -p 8082:80 nginx
docker run -d --name web3 -p 8083:80 nginx
# HAProxy frontend (port 80)
docker run -d --name lb \
-p 80:80 \
-v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg \
haproxy:latest
🎯 Production Deployment Checklist
Before deploying:
bash
# 1. Check port availability
sudo netstat -tulpn | grep 6400
# 2. Verify image exists
sudo docker images nginx:stable
# 3. Test locally first
sudo docker run --rm -p 6400:80 nginx:stable
curl http://localhost:6400
# 4. Check firewall rules
sudo firewall-cmd --list-ports
# 5. Plan restart policy
# --restart=always for production
Deployment command:
bash
sudo docker run -d \
--name beta \
--restart=always \
-p 6400:80 \
--memory="512m" \
--cpus="1.0" \
nginx:stable
Post-deployment verification:
bash
# 1. Container running
docker ps | grep beta
# 2. Port mapped
docker port beta
# 3. Service responding
curl -I http://localhost:6400
# 4. Check logs
docker logs beta
# 5. Monitor resources
docker stats beta --no-stream
📊 Monitoring and Management
View container stats:
bash
# Real-time stats
sudo docker stats beta
# Single snapshot
sudo docker stats beta --no-stream
Monitor nginx access:
bash
# Follow access logs
sudo docker logs -f beta
# Last 50 lines
sudo docker logs --tail 50 beta
# Logs with timestamps
sudo docker logs -t beta
Check container health:
bash
# Basic health check
sudo docker exec beta curl -f http://localhost:80 || echo "Service down"
# Detailed nginx status
sudo docker exec beta nginx -t
Port connectivity test:
bash
# From host
curl -I http://localhost:6400
# From another machine
curl -I http://stapp03:6400
# Check response time
time curl -s http://localhost:6400 > /dev/null
🔧 Managing the Container
Stop and start:
bash
# Stop container
sudo docker stop beta
# Start container
sudo docker start beta
# Restart container
sudo docker restart beta
Update restart policy:
bash
# Set to always restart
sudo docker update --restart=always beta
# Check current policy
sudo docker inspect beta --format='{{.HostConfig.RestartPolicy.Name}}'
View detailed info:
bash
# Full container config
sudo docker inspect beta
# Just network settings
sudo docker inspect beta --format='{{json .NetworkSettings}}' | jq
# Just port mappings
sudo docker inspect beta --format='{{json .HostConfig.PortBindings}}' | jq
Execute commands in container:
bash
# Shell access
sudo docker exec -it beta bash
# Check nginx config
sudo docker exec beta nginx -V
# View error log
sudo docker exec beta cat /var/log/nginx/error.log
🛡️ Security Considerations
1. Minimize exposed ports:
bash
# Only expose what's needed
# Bad: -p 80:80 -p 443:443 -p 8080:8080 -p 3306:3306
# Good: -p 80:80 -p 443:443 (web only)
2. Use localhost binding for internal services:
bash
# Database should not be public
docker run -p 127.0.0.1:5432:5432 postgres
3. Use firewall:
bash
# Allow only specific ports
sudo firewall-cmd --add-port=6400/tcp --permanent
sudo firewall-cmd --reload
4. Implement TLS:
bash
# Use HTTPS with certificates
docker run -d \
-p 443:443 \
-v /etc/ssl/certs:/etc/nginx/certs \
nginx:stable
5. Set resource limits:
bash
# Prevent resource exhaustion
docker run -d \
--name beta \
--memory="512m" \
--cpus="1.0" \
-p 6400:80 \
nginx:stable
🎉 Final Thoughts
You've successfully mastered Docker port mapping! This is fundamental for exposing containerized services:
What you accomplished:
✅ Pulled nginx:stable image from Docker Hub
✅ Created container named "beta"
✅ Mapped host port 6400 to container port 80
✅ Verified nginx serving on port 6400
✅ Container running and accessible
Real-world impact:
Service exposure: Make containerized apps accessible
Port flexibility: Use any host port regardless of container port
Network isolation: Container isolated but accessible
Multiple services: Run many containers on different ports
Production ready: Standard deployment pattern
Key lessons learned:
-p flag maps ports (host:container)
Port mapping creates iptables rules automatically
docker-proxy handles port forwarding
Can map multiple ports with multiple -p flags
EXPOSE in Dockerfile is documentation only
Port binding to specific interfaces for security
Container network isolation maintained
Best practices applied:
Used stable tag (not latest)
Descriptive container name
Non-privileged host port (6400)
Verified at each step
Checked accessibility
Container runs in background
This is production service deployment! Every web service, API, and application in containers uses port mapping! 💪
🚀 What's Next?
Day 43 complete! 🎉 You've mastered Docker port mapping!
Skills Mastered Today:
✅ Docker port mapping (-p flag)
✅ Image pulling with specific tags
✅ Container naming and management
✅ Network accessibility verification
✅ Production service deployment
Coming up: More Docker adventures - Docker volumes, Docker Compose orchestration, container health checks!
Day: 43/100
Challenge: KodeKloud Cloud DevOps
Date: December 18, 2025
Topic: Docker Port Mapping and Service Exposure
How do you manage port mappings in your environment? What's your container networking strategy? Share your Docker deployment practices! 🔌




