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.
| Driver | Environment | Description |
|---|---|---|
| bridge | Single host (default) | Creates virtual bridge (docker0) on host, isolated inter-container communication |
| host | Single host (high performance) | Container directly shares host network stack, no isolation |
| overlay | Multi-host (Swarm/K8s) | Distributed network spanning multiple Docker hosts |
| none | Complete isolation | No network interface, no external communication possible |
| macvlan | Legacy integration | Assigns 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 (
exposeonly, noports)
ports vs expose Differenceβ
Both directives "open" ports, but the scope differs.
| Directive | Scope | Purpose |
|---|---|---|
ports | Host β Container | Accessible from outside, binds host port |
expose | Between 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
exposeonly, 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