Docker Networking and Volumes¶
Networking: the four modes¶
Every container gets its own network namespace by default (see Docker Fundamentals) — what differs between the modes below is what that namespace is connected to.
Bridge (the default) — Docker creates a virtual bridge interface on
the host (docker0 for the default network, or a custom one for
user-defined networks). Each container gets a virtual ethernet pair: one
end inside the container's namespace, one end attached to the bridge.
The bridge acts like a virtual switch, and the host's kernel NATs
traffic between the bridge's private subnet and the outside world.
This is what actually happens when a request reaches
http://<host-ip>:8080 and gets routed to container A's port 80: the
kernel's NAT table (set up by docker run -p 8080:80) rewrites the
destination and forwards it onto the bridge, which delivers it to
container A's veth pair, which delivers it inside the container's
network namespace as if it arrived directly on port 80.
Host — the container shares the host's network namespace entirely. No bridge, no NAT, no port mapping — a process binding to port 80 inside the container is binding to port 80 on the host, directly. Fastest option (no NAT overhead), but the container loses all network isolation: it can bind any port the host allows, and it sees the host's actual network interfaces.
None — no networking at all beyond a loopback interface. Used for batch jobs that do no network I/O, or when a custom, unusual networking setup is being wired up manually.
Custom bridge networks (the practical default for multi-container
apps) — docker network create my-app-net creates a new bridge with
its own subnet, and containers attached to it get something the default
bridge doesn't provide: automatic DNS resolution by container name.
Why the default bridge isn't enough for multi-container apps¶
docker run -d --name db postgres:16
docker run -d --name api my-api-image
Both containers land on the default bridge network. Inside api,
trying to connect to db by hostname (postgres://db:5432/...) fails
to resolve — the default bridge network provides no built-in DNS
between containers, only raw IP connectivity, and even that requires
knowing the other container's IP in advance (which isn't stable across
restarts).
Fix: create a custom network and attach both containers to it.
docker network create my-app-net
docker run -d --name db --network my-app-net postgres:16
docker run -d --name api --network my-app-net my-api-image
Now, inside api, postgres://db:5432/... resolves correctly —
Docker runs an embedded DNS server for every custom network, mapping
each attached container's name to its current IP automatically,
including across container restarts (a restarted container may get a new
IP; the DNS entry follows it). This is the mechanism docker-compose
relies on to let services address each other by name (covered in a
follow-up page on Docker Compose).
Volumes: three ways to persist data, and when each applies¶
A container's writable layer is deleted with the container
(docker rm) — anything meant to outlive the container needs to live
somewhere else.
- Named volume (
docker volume create mydata, thendocker run -v mydata:/data ...) — Docker manages where on the host this actually lives (/var/lib/docker/volumes/...by default). The right default for anything a database needs to persist: Docker owns the lifecycle, it survivesdocker rm, and it's portable across different volume drivers (local disk, NFS, cloud block storage) without the container's own configuration changing. - Bind mount (
docker run -v /home/user/project:/app ...) — maps an exact, specific host path into the container. The right tool for local development: edit source code on the host, see the change immediately inside a running container without rebuilding the image. The wrong tool for production data, because it hardcodes a host filesystem path into the deployment, which doesn't generalize across different hosts. - tmpfs mount (
docker run --tmpfs /app/cache ...) — memory-backed, never touches disk, gone the moment the container stops. Right for short-lived secrets or scratch space that should never be persisted or swapped to disk at all.
Common pitfall¶
Publishing a port (-p 8080:80) is not the same as attaching to a
network, and mixing up the two mental models causes confusion: -p
exposes a container's port to the outside world through the host;
container-to-container communication on a shared custom network needs
neither -p nor knowledge of any host port at all — containers reach
each other directly through the bridge, by container name, using the
container's internal port. A container's own service should be
reachable from a sibling container even with zero -p flags, as long as
both are on the same custom network.