Skip to main content

Command Palette

Search for a command to run...

Day 43: Docker Port Mapping - Exposing Container Services 🔌

Published
22 min read

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:80

  • nginx:stable = image to use

What happened:

  1. Docker created container from nginx:stable

  2. Named it "beta"

  3. Created iptables rule: host:6400 → container:80

  4. Started nginx inside container

  5. 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! 🔌

More from this blog

🚀 DevOps Challenge- KodeKloud Solutions

73 posts