Docker's networking is straightforward once you know two things: user-defined bridge networks give you service-name DNS, the default bridge does not, and host.docker.internal is how a container reaches a service running on the host machine. Almost every "my containers can't talk to each other" question reduces to one of those.
The four network drivers worth knowing
| Driver | What it does | Default? |
|---|---|---|
bridge | Software bridge on the host. Each container gets a private IP. | Default for docker run containers that don't specify --network. |
Custom bridge (user-defined) | Same idea, but with service-name DNS between containers. | What you usually want. Compose creates one automatically per project. |
host | Container shares the host's network stack directly. | Linux-only-and-meaningful. |
none | No network. | Specialty use. |
The default bridge — why service-name DNS doesn't work
docker run -d --name web nginx puts the container on the default bridge network. You can reach it from the host via -p published ports, but another container on the default bridge can't reach web by name — they have to use IP addresses.
docker run -d --name web nginx:alpine
docker run --rm alpine ping web
# ping: bad address 'web'This is a historical Docker quirk. The default bridge predates Docker's internal DNS service, and the DNS service was added only to user-defined bridges to keep backward compatibility.
User-defined bridge networks — DNS works
Create one and use it:
docker network create app-net
docker run -d --name web --network app-net nginx:alpine
docker run --rm --network app-net alpine ping web
# PING web (172.18.0.2): ...Inside any container on app-net, web resolves to the web container's IP. The same applies to any name you've given a container with --name.
This is also what Compose does automatically — every Compose project creates a default user-defined bridge for its services, which is why db:3306 Just Works as a connection string from a sibling service.
Inspect networks
docker network ls # all networks
docker network inspect app-net # subnet, gateway, connected containers
docker network connect app-net other # attach a running container
docker network disconnect app-net other # detach
docker network rm app-net # remove (must be empty)
docker network prune # remove all unuseddocker inspect on a container also shows its network attachments:
docker inspect web --format '{{json .NetworkSettings.Networks}}'Host network
--network host skips the bridge entirely and gives the container the host's network stack. The container's localhost is the host's localhost, and ports the container binds are bound directly on the host (no -p needed).
docker run -d --network host nginx:alpineProperties:
- Fast. No bridge or NAT in the path.
- Linux-meaningful, Mac/Windows limited. On Docker Desktop, "host" is the Docker Desktop Linux VM, not your Mac or Windows host. So
hostnetworking works inside the VM but doesn't give containers direct access to Mac/Windows ports. - No port isolation. Container's binds collide directly with host's.
- Not great for multi-tenant hosts. All containers share one network stack.
For Mac and Windows, host networking has been worked-around-incomplete for years. Recent Docker Desktop releases added a beta "host networking" mode for Mac that approximates the Linux behavior; check if your version supports it.
Reaching the host from inside a container
The other direction: a container needs to talk to a service running on your host (a database server you're running natively, an API on localhost:8000).
Mac and Windows (Docker Desktop): use host.docker.internal. It resolves to the host's IP from inside any container, on any network.
docker run --rm alpine ping host.docker.internalLinux: host.docker.internal is not automatic. Two paths:
-
Add it explicitly when running the container:
bashdocker run --rm --add-host host.docker.internal:host-gateway alpine ping host.docker.internalThe magic
host-gatewaytoken resolves to the bridge gateway IP, which is the host from the container's perspective. -
In Compose:
yamlservices: app: extra_hosts: - "host.docker.internal:host-gateway"
host-gateway works on Docker 20.10+.
Port publishing — -p semantics
docker run -d -p HOST_PORT:CONTAINER_PORT imageThe container listens on its own port (inside its own network namespace). -p publishes that port on the host so the host (and the host's network) can reach it. Without -p, the container is only reachable from other containers on the same Docker network.
The HOST_PORT side can take a specific interface:
docker run -d -p 127.0.0.1:5432:5432 postgres:17That binds to the host's loopback only — useful when you want a database reachable from the host but not from your LAN.
Container-to-container communication on a user-defined bridge does not need -p. Two containers on the same network reach each other by service name on the container port directly. -p is for traffic from outside the Docker network.
Compose service-name DNS
In a Compose stack, every service is reachable from every other service by its service name:
services:
web:
image: my-app
environment:
DATABASE_URL: postgres://postgres:hello@db:5432/myapp
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: helloFrom inside web, db:5432 resolves to the Postgres container. No port publishing needed for this traffic. Publish ports only if you want host or external access.
This works because Compose creates a user-defined bridge network for the project and attaches every service to it.
Multiple networks per container
A container can sit on more than one network — useful for isolating tiers:
services:
web:
networks: [frontend, backend]
app:
networks: [backend, db-tier]
db:
networks: [db-tier]
networks:
frontend:
backend:
db-tier:web can reach app (both on backend). app can reach db (both on db-tier). web cannot reach db (no shared network). Clean tier separation.
Common pitfalls
- "Container can't ping by name." They're on the default bridge. Create a user-defined network and put both on it (or use Compose, which does this automatically).
localhostdoesn't mean the host inside a container. It means the container's own loopback. Usehost.docker.internal(Docker Desktop) or--add-host host.docker.internal:host-gateway(Linux).-p 5432:5432and a container can't reach Postgres athost:5432. Yes — because the container is on a Docker network, andhost:5432from inside the container goes to its own loopback. From a sibling container, use the service name (db:5432), not the published port.- Port already in use on the host. The container can't bind because something on the host (or another container) already has the port. Pick a different host port.
- DNS works on first start but stops after a
docker network disconnect. Reconnect or recreate the container; the network info is set at container creation time.
What to do next
- Docker Compose: Getting Started — how Compose builds the user-defined network for you.
- docker run Cheat Sheet — the
-pand--networkflags in context. - Cannot Connect to the Docker Daemon — the related daemon-socket connectivity issue.
FAQ
Sources
Authoritative references this article was fact-checked against.
- Networking overview — Docker docsdocs.docker.com
- Bridge network driver — Docker docsdocs.docker.com

