🌐 Container Networking

Containers Series: Part 1 — Podman Basics  |  Part 2 — Docker Basics  |  Part 3 — Container Networking

How Container Networking Works

Container networking is built on Linux network namespaces — each container gets its own isolated network stack with its own interfaces, routing table, and firewall rules. The container engine (Docker or Podman) creates virtual network infrastructure on the host to connect containers to each other and to the outside world.

Understanding container networking is essential for troubleshooting connectivity problems, securing multi-container applications, and designing systems where containers need to communicate. The concepts apply equally to Docker and Podman.

Network Driver Types

bridge (default)

Software bridge on the host. Containers get private IPs. NAT used for outbound. Port mapping for inbound.

Use: Single-host multi-container apps.

host

Container shares the host's network namespace. No isolation — uses host IP directly. Best performance.

Use: When performance matters and isolation isn't needed.

none

No network interface except loopback. Complete network isolation.

Use: Batch jobs, security-sensitive workloads.

overlay

Multi-host networking. Spans multiple Docker hosts. Requires Swarm or Kubernetes.

Use: Distributed multi-host deployments.

macvlan

Container gets its own MAC address and appears as a physical device on the network.

Use: Legacy apps needing a real network presence.

ipvlan

Similar to macvlan but shares host MAC. Better for environments that restrict MAC spoofing.

Use: Cloud environments, strict switch configs.

Bridge Network (default) — how containers reach the outside: Internet / LAN | Host eth0 (192.168.1.100) | docker0 bridge (172.17.0.1) | ┌─────┴─────┐ C1 C2 172.17.0.2 172.17.0.3 Containers talk to each other via bridge. Outbound traffic: NAT through host eth0. Inbound traffic: port mapping (-p 8080:80).

Examples

1
Manage Docker Networks
# List all networks
docker network ls

# Inspect the default bridge network
docker network inspect bridge

# Create a custom bridge network
docker network create mynet
docker network create --driver bridge mynet

# Create with specific subnet and gateway
docker network create \
    --driver bridge \
    --subnet 172.20.0.0/16 \
    --gateway 172.20.0.1 \
    --ip-range 172.20.1.0/24 \
    appnet

# Run container on a specific network
docker run -d --name web --network appnet nginx:latest
docker run -d --name db  --network appnet mysql:8.0

# Connect a running container to a network
docker network connect appnet mycontainer

# Disconnect container from network
docker network disconnect appnet mycontainer

# Remove a network (all containers must be disconnected)
docker network rm mynet
docker network prune   # remove all unused networks
docker network ls output:
NETWORK ID NAME DRIVER SCOPE a1b2c3d4e5f6 bridge bridge local b2c3d4e5f6a1 host host local c3d4e5f6a1b2 none null local d4e5f6a1b2c3 appnet bridge local
2
Container DNS — How Containers Find Each Other

On custom bridge networks, Docker provides automatic DNS resolution by container name — the killer feature that makes multi-container apps work cleanly:

# Create a network
docker network create appnet

# Start a database container
docker run -d \
    --name postgres \
    --network appnet \
    -e POSTGRES_PASSWORD=secret \
    postgres:15

# Start app container on same network
docker run -d \
    --name webapp \
    --network appnet \
    -e DATABASE_URL=postgresql://postgres:secret@postgres:5432/mydb \
    myapp:latest

# The app connects to 'postgres' by NAME — not by IP
# Docker DNS resolves 'postgres' to the container's IP automatically

# Verify DNS works from inside a container
docker exec webapp ping postgres
docker exec webapp nslookup postgres
docker exec webapp curl http://nginx/health
Always use custom networks — never the default bridge. The default bridge network does NOT have automatic DNS. Containers on the default bridge can only reach each other by IP address. On a custom bridge network, containers find each other by name. This is why every Docker Compose stack creates its own network automatically.
Network aliases: A container can have additional DNS names on a network:
docker run -d --name mydb --network appnet \
    --network-alias database \
    --network-alias db \
    postgres:15
Now mydb, database, and db all resolve to the same container.
3
Port Mapping — Exposing Containers to the Host
# Map host port 8080 to container port 80
docker run -d -p 8080:80 nginx:latest

# Map on a specific host interface only (more secure)
docker run -d -p 127.0.0.1:8080:80 nginx:latest
docker run -d -p 192.168.1.100:8080:80 nginx:latest

# Map multiple ports
docker run -d \
    -p 80:80 \
    -p 443:443 \
    nginx:latest

# Publish ALL exposed ports to random host ports
docker run -d -P nginx:latest

# Check what ports are mapped
docker port mycontainer
docker ps   # PORTS column shows mappings

# Map UDP port
docker run -d -p 5353:5353/udp dns:latest
docker port output:
80/tcp -> 0.0.0.0:8080 443/tcp -> 0.0.0.0:8443
💡 Bind to 127.0.0.1 for internal services. -p 8080:80 binds to all interfaces (0.0.0.0) — the port is reachable from anywhere. -p 127.0.0.1:8080:80 binds only to localhost — only processes on the same host can reach it. Use this for services that should only be accessed via a reverse proxy.
4
Host Network Mode

The container shares the host's network namespace — no NAT, no port mapping needed, maximum performance:

# Run with host networking
docker run -d --network host nginx:latest

# The container now listens on the host's port 80 directly
# No -p flag needed or allowed with --network host
curl http://localhost:80

# Podman equivalent
podman run -d --network host nginx:latest

# Verify — container sees host interfaces
docker run --rm --network host alpine ip addr
⚠️ No isolation with host networking. The container can bind to any port on the host, see all host network interfaces, and connect to any local service. Only use host networking when performance is critical and you trust the container image completely. Not compatible with rootless Podman on all systems.
5
Podman Network Management

Podman uses the same networking concepts as Docker but with slightly different commands and the netavark/CNI backend:

# List networks
podman network ls

# Create a network
podman network create appnet
podman network create --subnet 172.20.0.0/16 appnet

# Inspect a network
podman network inspect appnet

# Run containers on the network
podman run -d --name db --network appnet postgres:15
podman run -d --name web --network appnet nginx:latest

# Connect/disconnect
podman network connect appnet mycontainer
podman network disconnect appnet mycontainer

# Remove network
podman network rm appnet
podman network prune

# Rootless Podman — check the slirp4netns or pasta backend
podman info | grep -A5 "network"

# DNS works same as Docker on custom networks
podman exec webapp ping db
Rootless networking differences: Rootless Podman uses slirp4netns or pasta for networking instead of kernel bridge interfaces. This means rootless containers cannot bind to privileged ports (below 1024) and have slightly different performance characteristics than root containers.
6
Networking in Docker Compose

Compose automatically creates a network for each stack — all services in the stack can reach each other by service name:

# docker-compose.yml — explicit network configuration
cat > docker-compose.yml << 'EOF'
services:
  nginx:
    image: nginx:alpine
    networks:
      - frontend
    ports:
      - "80:80"

  app:
    image: myapp:latest
    networks:
      - frontend
      - backend

  db:
    image: postgres:15
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true    # no external access
EOF

# internal: true means the backend network has no internet access
# app can reach both nginx (frontend) and db (backend)
# db can only be reached by app — not by nginx or external traffic

docker compose up -d

# View networks created by compose
docker network ls | grep myproject
💡 Network segmentation matters. Putting your database on an internal: true network means it has no outbound internet access and is unreachable from the host network — only containers explicitly on that network can talk to it. This is proper defense-in-depth for containerized applications.
7
Troubleshooting Container Networking
# Check what networks a container is on
docker inspect mycontainer | jq '.[0].NetworkSettings.Networks'

# Get a container's IP address
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mycontainer

# Test connectivity from inside a container
docker exec mycontainer ping google.com
docker exec mycontainer curl -v http://db:5432
docker exec mycontainer nslookup db

# Run a diagnostic container on the same network
docker run --rm -it --network appnet nicolaka/netshoot
# netshoot has ping, nslookup, curl, tcpdump, nmap, ss, etc.

# Watch container traffic with tcpdump on the host
# Find the veth interface for your container
docker inspect mycontainer | grep -i "sandboxkey\|veth"
sudo tcpdump -i veth123abc -n

# Check iptables rules Docker created
sudo iptables -t nat -L -n --line-numbers | grep -A5 DOCKER
sudo iptables -L DOCKER -n

# Check host routing table
ip route show
ip route show table all | grep docker
nicolaka/netshoot is the container troubleshooting Swiss Army knife — it contains every network diagnostic tool you could want in a small image. Run it on the same network as the container you're debugging and you have a fully equipped diagnostic environment without installing anything on the host.
8
macvlan — Containers on the Physical Network

macvlan gives containers their own MAC address and makes them appear as physical devices on your LAN — useful for legacy apps that need a real network presence:

# Create a macvlan network
docker network create \
    --driver macvlan \
    --subnet 192.168.1.0/24 \
    --gateway 192.168.1.1 \
    --ip-range 192.168.1.192/27 \
    -o parent=eth0 \
    macnet

# Run a container with a specific IP on the LAN
docker run -d \
    --name legacy-app \
    --network macnet \
    --ip 192.168.1.200 \
    mylegacyapp:latest

# The container now has 192.168.1.200 on your LAN
# Other hosts on the LAN can ping and connect to it directly
ping 192.168.1.200

# Enable promiscuous mode on the host interface
sudo ip link set eth0 promisc on
⚠️ macvlan quirk: The host cannot communicate with macvlan containers directly — this is a kernel limitation. The host and container are on the same LAN but the host cannot reach the container by its macvlan IP. Other hosts on the LAN can. Work around by creating a macvlan interface on the host itself if needed.

Quick Reference

CommandWhat it does
docker network lsList all networks
docker network create NAMECreate a custom bridge network
docker network inspect NAMEDetailed network information
docker network connect NET CONTAINERAdd container to network
docker network disconnect NET CONTAINERRemove container from network
docker network rm NAMERemove a network
docker network pruneRemove all unused networks
docker run --network NAMERun container on specific network
docker run --network hostUse host network namespace
docker run --network noneNo network access
docker run -p HOST:CONTMap host port to container port
docker run -p 127.0.0.1:H:CMap to localhost only
docker port CONTAINERShow port mappings
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' CGet container IP
docker run --rm -it --network NET nicolaka/netshootDiagnostic container on network

Network Driver Comparison

DriverIsolationPerformanceBest For
bridgeGoodGoodDefault — single host multi-container
hostNoneBestPerformance-critical, trusted images
noneCompleteN/ABatch jobs, maximum security
overlayGoodGoodMulti-host Swarm/Kubernetes
macvlanGoodBestLegacy apps needing LAN presence
ipvlanGoodBestEnvironments restricting MAC spoofing

Container DNS Summary

ScenarioDNS Available?Resolution
Default bridge network❌ NoIP addresses only — use --link (deprecated)
Custom bridge network✅ YesContainer name resolves automatically
Docker Compose stack✅ YesService name resolves automatically
Network alias✅ YesAdditional DNS names per container
Host networkHost DNSUses host /etc/resolv.conf

← Back to Containers Index ↑ Back to EXPANDED