Docker network modes explained with practical examples
Container networking confuses engineers because the abstraction leaks. DNS works between containers sometimes but not others. Ports published on one container are not reachable from another. A service running on localhost inside a container is not reachable from another container using localhost. Understanding why requires understanding what each network mode actually does.
Bridge mode (the default)
The default network mode creates a virtual network interface on the host (docker0). Containers on the same bridge network can reach each other by name. Containers on different bridge networks cannot.
# Create a custom bridge network (always do this — don't use default bridge)
docker network create myapp
# Run containers on the same network
docker run -d --name api --network myapp myapp-api
docker run -d --name db --network myapp postgres:16
# From api, you can reach db by name
# Inside api container: psql -h db -U postgres
# "db" resolves via Docker's embedded DNS
Important: The default docker0 bridge does NOT support DNS resolution by name. Always create a custom bridge network. This is the most common cause of "containers can't find each other" confusion.
# Docker Compose creates a custom bridge network per project automatically
# Services can reference each other by service name
services:
api:
image: myapp-api
ports:
- "3000:3000" # expose to host
db:
image: postgres:16
# NOT exposed to host — only reachable by "api" service internally
# api can connect to db with hostname "db"
# db is NOT accessible from the host machine (no ports published)
Host mode
The container shares the host's network namespace. No network isolation, no port mapping needed.
# Container runs as if it is on the host network directly
docker run --network host myapp-api
# The service binds directly to host port 3000
# No -p flag needed (or even possible) in host mode
# Useful for:
# - Performance-sensitive workloads (no NAT overhead)
# - When the container needs access to host network interfaces directly
# - Monitoring tools that need to see all host traffic
Host mode works on Linux only. On macOS and Windows, Docker runs in a VM, so --network host connects to the VM's network, not your Mac's network. This causes confusion when services are unreachable from the host machine.
None mode
# Container has no network interfaces (except loopback)
docker run --network none my-isolated-task
# Use for:
# - Security-sensitive data processing jobs
# - Build containers that should not have internet access
# - Any workload where you want to guarantee no network egress
Overlay mode (multi-host networking)
Overlay networks span multiple Docker hosts. Required for Docker Swarm. Containers on different physical machines can communicate as if on the same network.
# Create an overlay network (requires Swarm mode)
docker network create --driver overlay --attachable myapp-overlay
# Deploy a service to the overlay network
docker service create --name api --network myapp-overlay --replicas 3 myapp-api
# All 3 replicas can communicate via the overlay network
# regardless of which host they run on
Common networking problems and fixes
Problem: Container cannot reach a service on the host
# Wrong: localhost inside a container refers to the container, not the host
# curl http://localhost:5432 from inside a container — fails
# Correct: use the special host-gateway DNS name
# Linux:
docker run --add-host=host.docker.internal:host-gateway myapp
# macOS/Windows: host.docker.internal works out of the box
# curl http://host.docker.internal:5432
Problem: Two Compose projects cannot communicate
# Project A: docker-compose.yml
services:
frontend:
networks:
- shared
networks:
shared:
name: shared-network # explicit name
driver: bridge
---
# Project B: docker-compose.yml
services:
api:
networks:
- shared
networks:
shared:
external: true # reference the existing network
name: shared-network
Problem: Port conflict on the host
# Bind to a specific host interface to avoid conflicts
services:
api:
ports:
- "127.0.0.1:3000:3000" # only accessible from localhost
# Not "0.0.0.0:3000:3000" (accessible from anywhere)
db:
ports:
- "127.0.0.1:5432:5432" # local dev access only
Inspecting network configuration
# List all networks
docker network ls
# Inspect a network — shows connected containers and their IPs
docker network inspect myapp
# See which networks a container is connected to
docker inspect myapp-api | jq '.[0].NetworkSettings.Networks'
# Test connectivity from inside a container
docker exec -it myapp-api ping db
docker exec -it myapp-api curl http://db:3000/health
The two rules that cover 90% of networking issues: always use custom bridge networks (never the default bridge), and never use localhost to communicate between containers. Follow those two rules and Docker networking becomes straightforward.