You run two containers. One is your app, the other is your database. Your app can’t connect to the database. You spend 30 minutes googling “docker container connection refused” before realizing they’re on different networks.
Docker networking is one of those things that “just works” until it doesn’t. Here’s how it actually works under the hood.
The default: bridge network
When you run docker run without specifying a network, Docker connects your container to the default bridge network. This is a virtual network that Docker creates on your host machine.
docker network ls
# NETWORK ID NAME DRIVER SCOPE
# a1b2c3d4e5f6 bridge bridge local
# f6e5d4c3b2a1 host host local
# 1a2b3c4d5e6f none null local
Every container on the bridge network gets its own IP address (typically 172.17.0.x). Containers on the same bridge can talk to each other by IP — but NOT by container name. This is the source of 90% of Docker networking confusion.
# This works (by IP)
docker exec app curl http://172.17.0.3:5432
# This does NOT work on the default bridge
docker exec app curl http://database:5432
Why container names don’t work (and how to fix it)
The default bridge network doesn’t have DNS resolution. Container names mean nothing. To get name-based resolution, create a custom network:
docker network create myapp
docker run --network myapp --name database postgres
docker run --network myapp --name app myapp-image
Now app can reach database by name. Docker’s embedded DNS server resolves container names to IP addresses within custom networks. This is why Docker Compose works so smoothly — it creates a custom network for each project automatically.
The four network modes
Bridge (default)
Each container gets its own network namespace with a virtual ethernet interface connected to a bridge on the host. Containers are isolated from the host network but can reach the internet through NAT.
Use when: Most cases. Running web apps, databases, background workers.
Host
The container shares the host’s network namespace. No isolation — the container uses the host’s IP and ports directly.
docker run --network host nginx
# Nginx is now on host port 80, no -p flag needed
Use when: You need maximum network performance (no NAT overhead) or the container needs to see all host network traffic. Common for monitoring tools.
Risk: Port conflicts. If the host already uses port 80, the container can’t bind to it.
None
The container has no network access at all. Only the loopback interface exists.
docker run --network none alpine ping google.com
# ping: bad address 'google.com'
Use when: Security-sensitive workloads that should never make network calls. Batch processing jobs that only read/write files.
Overlay
Spans multiple Docker hosts. Containers on different machines can communicate as if they’re on the same network. Used in Docker Swarm and Kubernetes.
Use when: Multi-host deployments. You probably don’t need this for local development.
Port mapping explained
-p 8080:3000 means “forward host port 8080 to container port 3000.” The container listens on 3000 internally; the outside world connects on 8080.
docker run -p 8080:3000 myapp
# Host:8080 → Container:3000
Common gotcha: -p 3000:3000 and -p 3000 are different. The first maps host 3000 to container 3000. The second maps a random host port to container 3000.
Docker Compose networking
Docker Compose creates a network named {project}_default automatically. All services in the compose file join this network and can reach each other by service name.
services:
app:
build: .
ports:
- "3000:3000"
db:
image: postgres
# No ports needed — app reaches db by name "db" on port 5432
redis:
image: redis
# Same — app reaches redis by name "redis" on port 6379
You only need ports for services that the outside world needs to reach. Internal service-to-service communication uses container names and doesn’t need port mapping.
Debugging Docker networking
# See which network a container is on
docker inspect mycontainer | grep NetworkMode
# See all containers on a network
docker network inspect myapp
# Test connectivity from inside a container
docker exec mycontainer ping database
docker exec mycontainer curl http://api:3000/health
# See what ports are mapped
docker port mycontainer
The one-sentence summary
Docker creates virtual networks with their own IP ranges and DNS. Use custom networks (not the default bridge) so containers can find each other by name. Use port mapping only for services that need to be reachable from outside Docker.
Related: Docker cheat sheet · Docker Compose cheat sheet · How Docker Containers Actually Work · Docker vs Kubernetes · How Environment Variables Actually Work