iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Port Mapping

Docker isolates each container in its own network namespace. To expose a service to the host (or beyond), you publish ports with -p. Knowing the difference between EXPOSE (documentation), -p (published), and Compose port mappings keeps your services reachable without leaking everything to the network.

Publish, bind, EXPOSE, networks, compose

EXAMPLE
# 1) The simplest case
docker run -p 8080:80 nginx
# Format: -p HOST_PORT:CONTAINER_PORT
# Visit http://localhost:8080 → reaches nginx on container port 80.

# 2) Bind to a specific host interface
docker run -p 127.0.0.1:8080:80 nginx           # localhost only — invisible from network
docker run -p 192.168.1.10:8080:80 nginx        # bind to a single IP
docker run -p 0.0.0.0:8080:80 nginx              # default — ALL interfaces

# Use 127.0.0.1 binding by default for dev to avoid accidentally exposing services to the LAN.

# 3) Random host port
docker run -P nginx                              # publish all EXPOSEd ports on random host ports
docker port <container>                          # show the mapping

# 4) Multiple ports
docker run -p 8080:80 -p 8443:443 nginx
docker run -p 8080:80/tcp -p 5353:53/udp myapp   # explicit protocol

# 5) Port range
docker run -p 5000-5005:5000-5005 myapp           # 6 ports

# 6) EXPOSE in Dockerfile — DOCUMENTATION ONLY
FROM node:20-alpine
EXPOSE 3000
CMD ['node', 'server.js']
# EXPOSE doesn't publish anything to the host. It signals to humans + tools (-P) what the container LISTENS on.
# You still need -p at runtime to make it reachable from outside.

# 7) Compose port mappings
services:
    web:
        image: nginx
        ports:
            - '8080:80'                          # host:container
            - '127.0.0.1:8443:443'                # bind to host loopback
            - '5000-5005:5000-5005'               # range
            - '8090:80/tcp'
            - '5353:53/udp'
    api:
        build: ./api
        expose: ['3000']                          # not published; reachable from other services in same network

# 8) expose: in compose vs ports:
# • ports — publishes to host network
# • expose — only makes the port reachable INSIDE the compose network (between services)
# Use expose for backend services that only the front-end calls; ports for things users hit.

# 9) Container-to-container networking — no port mapping needed
# In a user-defined network, services reach each other by name on the container's listening port.
services:
    web:    { build: ./web, ports: ['80:80'] }
    api:    { build: ./api, expose: ['3000'] }
    db:     { image: postgres:16, expose: ['5432'] }

# In web's code: fetch('http://api:3000/'), connect to db:5432.

# 10) Networks
docker network create app-net
docker run -d --network app-net --name db postgres
docker run -d --network app-net --name api -p 3000:3000 myapi
# api reaches db at hostname 'db' on port 5432; outside reaches api at host 3000.

# 11) Listing + inspecting
docker ps                                          # current PORTS column
docker port <name>                                  # mappings
docker network inspect bridge                       # routing rules
docker exec -it api netstat -tlnp                    # what's listening inside

# 12) IPv6
# Default daemon disables IPv6; enable in /etc/docker/daemon.json:
{
    "ipv6": true,
    "fixed-cidr-v6": "2001:db8:1::/64"
}
# Then -p 8080:80 publishes on both v4 and v6 (or use [::]:8080:80 explicitly).

# 13) Host network mode (Linux only)
docker run --network host nginx
# Skip the port mapping; container shares host network namespace.
# Pros: faster; cons: no isolation; same port conflicts as bare metal. Use sparingly.

# 14) Macvlan / IPvlan — container gets its own IP on the LAN
# Advanced. Use when you need containers visible like physical hosts.

# 15) Common bugs
# • Reachable from LAN by accident — default 0.0.0.0; bind 127.0.0.1 unless you mean public
# • Port already in use — pick another host port; docker doesn't auto-pick
# • Connection refused inside compose — using 'localhost' from one service to another instead of service name
# • Wrong protocol — UDP service published as TCP; specify /udp
# • Different host port between compose runs — pin in compose to avoid 'works on my machine'
# • Windows / Mac Docker Desktop — host networking limited; LAN access works via the VM IP
# • SELinux / firewall blocking — check ufw / iptables for blocked ports
# • Exposing 0.0.0.0 in production — firewall + WAF in front, or bind to private IP only

Why it matters

Use -p HOST:CONTAINER to publish ports, bind to 127.0.0.1 in dev to avoid LAN exposure, and let containers talk via Compose service names instead of mapping every internal port. EXPOSE is documentation; ports: publishes; expose: in compose keeps the port internal to the network.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
docker run -d -p 8080:3000 my-api
# host:container
Try it Yourself »

Exercise

Map host port 8080 to container port 3000.

docker run 8080:3000 my-api

Discussion

Loading…