Skip to main content

Container Networking

Container networking is the core infrastructure that controls communication between containers in a Docker environment. Proper network design simultaneously achieves security (preventing unnecessary external port exposure), stability (isolation between services), and maintainability (DNS-based service discovery). This chapter systematically covers everything from Docker network types to practical multi-network configurations and debugging techniques.


Docker Network Types

Docker provides various network drivers, and you should choose the appropriate driver based on your use case.

DriverEnvironmentDescription
bridgeSingle host (default)Creates virtual bridge (docker0) on host, isolated inter-container communication
hostSingle host (high performance)Container directly shares host network stack, no isolation
overlayMulti-host (Swarm/K8s)Distributed network spanning multiple Docker hosts
noneComplete isolationNo network interface, no external communication possible
macvlanLegacy integrationAssigns real MAC address to container, connects directly to physical network

bridge Network (Default)

The most common driver for inter-container communication on a single Docker host.

Host OS
┌─────────────────────────────────────────┐
│ │
│ docker0 (bridge: 172.17.0.1) │
│ │ │
│ ├── nginx (172.17.0.2) │
│ ├── app (172.17.0.3) │
│ └── db (172.17.0.4) │
│ │
└─────────────────────────────────────────┘

Characteristics of the default bridge network:

  • Containers can communicate with each other via IP address
  • Default bridge (docker0) does not support DNS name resolution(must use IP directly)
  • User-defined bridge networks support automatic DNS resolution by container name
# Check default bridge network
docker network ls

# Detailed info for default bridge (docker0)
docker network inspect bridge

host Network

The container directly uses the host's network stack. No port mapping is needed and performance is best, but there is no isolation, making it vulnerable to security issues.

services:
app:
image: myapp:latest
network_mode: host # Directly use host network

none Network

A state where network interfaces are completely removed. Used for batch jobs like file processing or encryption that require absolutely no external communication.

docker run --network none myapp:latest

Key Advantages of User-Defined bridge Networks

Networks created by Docker Compose are all user-defined bridge networks. They provide important additional features compared to the default docker0 bridge.

Automatic DNS Name Resolution

In user-defined bridge networks, a container's service name becomes its DNS hostname.

# If the container name is 'db', it's accessible as 'db' internally
# Spring Boot application.properties example:
# spring.datasource.url=jdbc:postgresql://db:5432/myapp_db
# ↑↑
# Container service name = DNS

# In Nginx upstream:
# upstream spring_app {
# server app:8080; ← 'app' service is automatically resolved to IP
# }

Per-Network Isolation

Containers belonging to different networks cannot communicate. A single container can participate in multiple networks simultaneously.


Docker Compose Automatic Network Creation

When no networks section is defined, Docker Compose automatically creates a default network prefixed with the project name and connects all services to it.

# Without networks section → {project}_default is auto-created
services:
nginx:
image: nginx:alpine
app:
image: myapp:latest
db:
image: postgres:16-alpine
# If project name is 'myproject':
# myproject_default network auto-created
# All services connected to the same network

docker network ls
# NETWORK ID NAME DRIVER SCOPE
# abc123 myproject_default bridge local

Disadvantage of auto-created networks: all services share the same network, making the DB accessible from external sources. Explicit multi-network configuration is recommended.


Multi-Network Configuration: Separating frontend / backend

In real production environments, separate networks to completely block unnecessary communication.

┌──────────────────────────────────────────────────────────┐
│ Host OS │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ frontend network (bridge) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌──────────────────────┐ │ │
│ │ │ Nginx │───────►│ Spring Boot │ │ │
│ │ │ :80, :443 │ │ :8080 │ │ │
│ │ └─────────────┘ └──────────────────────┘ │ │
│ │ ▲ │ │ │
│ └──────────┼──────────────────────────┼───────────────┘ │
│ │ Port exposure │ │
│ External traffic │ │
│ ┌─────────────▼────────────────┐ │
│ │ backend network (bridge) │ │
│ │ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ Spring Boot │ │ │
│ │ │ :8080 │ │ │
│ │ └──────────┬───────────┘ │ │
│ │ │ │ │
│ │ ┌──────────▼───────────┐ │ │
│ │ │ PostgreSQL │ │ │
│ │ │ :5432 │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
version: '3.9'

services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
networks:
- frontend # Participates in frontend network only

app:
build: .
expose:
- "8080"
networks:
- frontend # Communicates with Nginx
- backend # Communicates with DB

db:
image: postgres:16-alpine
expose:
- "5432"
networks:
- backend # Participates in backend network only (no external access)

networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # Containers on this network cannot access external internet

Core isolation rules:

  • Nginx cannot directly access DB (not on the same network)
  • DB cannot access external internet (internal: true)
  • DB port (5432) is completely inaccessible from outside (expose only, no ports)

ports vs expose Difference

Both directives "open" ports, but the scope differs.

DirectiveScopePurpose
portsHost ↔ ContainerAccessible from outside, binds host port
exposeBetween containers (internal)Communication within container network only
# ports: accessible from outside via host port 8080
ports:
- "8080:8080" # host:container

# expose: accessible only from other containers on the same network
expose:
- "8080" # Not accessible from outside

Practical principles:

  • Nginx: expose 80, 443 externally via ports
  • Spring Boot, DB: use expose only, block external access

Service Discovery (DNS)

In user-defined networks, Docker automatically converts container names to IP addresses through its built-in DNS server.

# DNS verification from inside Spring Boot container
docker compose exec app ping db # 'db' service name resolved to IP
docker compose exec app nslookup db # Check DNS query result

# DNS verification for app service from Nginx container
docker compose exec nginx ping app
docker compose exec nginx wget -O- http://app:8080/actuator/health

DNS resolution rules:

  • service name → internal IP of that container
  • When scaled up (--scale app=3) → automatic load balancing via DNS round-robin
  • Even if IP changes due to container restart, DNS auto-updates

overlay Network (Docker Swarm)

Used in Docker Swarm environments that deploy containers across multiple Docker hosts. Containers on physically different servers can communicate as if they're on the same network.

# Initialize Swarm
docker swarm init --advertise-addr 192.168.1.10

# Join worker node
docker swarm join --token <token> 192.168.1.10:2377

# Create overlay network
docker network create \
--driver overlay \
--attachable \
my-overlay-network
# docker-compose.yml (for Swarm deployment)
version: '3.9'

services:
nginx:
image: nginx:alpine
networks:
- overlay-network
deploy:
replicas: 2
placement:
constraints:
- node.role == worker

networks:
overlay-network:
driver: overlay
attachable: true

Overlay networks use VXLAN tunneling to connect containers on different hosts. For single hosts, bridge networks are simpler and perform better.


Network Debugging

docker network inspect

View detailed network information (connected containers, IP ranges, driver configuration, etc.).

# List all networks
docker network ls

# Detailed info for a specific network
docker network inspect myproject_frontend

# Check connected containers and IPs
docker network inspect myproject_backend --format \
'{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{println}}{{end}}'

Connection Testing via docker exec

# Ping another container from inside a container
docker compose exec nginx ping app
docker compose exec nginx ping db # nginx (only in frontend) cannot access db

# HTTP connection test with curl
docker compose exec nginx curl -v http://app:8080/actuator/health

# Check open ports with netstat
docker compose exec app netstat -tlnp

# Trace route with traceroute
docker compose exec app traceroute db

Systematic Diagnosis of Network Connectivity Issues

# 1. Verify containers are running
docker compose ps

# 2. Check if they belong to the same network
docker network inspect myproject_backend

# 3. Check firewall rules (host)
sudo iptables -L -n | grep DOCKER

# 4. Check connection errors in container logs
docker compose logs app | grep -i "connection refused\|timeout\|refused"

# 5. Verify DNS resolution
docker compose exec app nslookup db
docker compose exec app cat /etc/resolv.conf

Security Perspective: Preventing Unnecessary External Port Exposure

Checklist

# Check currently open ports on host (external exposure status)
netstat -tlnp | grep docker
# or
ss -tlnp | grep docker

Incorrect configuration (security risk):

# ❌ Directly exposing DB port to host - absolutely forbidden
db:
image: postgres:16-alpine
ports:
- "5432:5432" # DB directly accessible from outside!

Correct configuration:

# ✅ Use expose only to allow internal container communication
db:
image: postgres:16-alpine
expose:
- "5432" # Only accessible within container network
networks:
- backend # Participates in backend network only

Additional security hardening:

# When you need temporary direct DB access in dev:
# docker-compose.override.yml (dev only)
services:
db:
ports:
- "127.0.0.1:5432:5432" # Localhost only (never use 0.0.0.0)

0.0.0.0:5432:5432 is accessible from all interfaces, so always restrict to localhost with 127.0.0.1:5432:5432.


Expert Tips

1. Explicitly specify network names

networks:
frontend:
name: myapp-frontend # Specify actual Docker network name (no project prefix)
backend:
name: myapp-backend
internal: true

2. Reuse existing external networks

When multiple Docker Compose projects need to share the same network:

networks:
shared-network:
external: true # Use an already existing network
name: myapp-frontend

3. IPv6 support

networks:
frontend:
driver: bridge
enable_ipv6: true
ipam:
config:
- subnet: "2001:db8::/64"

4. Resolving network range conflicts

When company internal network and Docker ranges conflict:

networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/24 # Specify custom subnet
gateway: 172.30.0.1