Volumes and Networking

Named volumes vs bind mounts and how containers communicate over Docker networks.

The problem: containers are ephemeral

A container's writable layer disappears the moment the container is removed — anything an application writes inside the container (uploaded files, a database's data directory) is lost along with it. Volumes solve persistence; Docker networks solve how containers talk to each other. Both are essential the moment your app is more than a single stateless process.

Named volumes vs. bind mounts

Docker gives you two different ways to attach storage from outside the container, and they solve different problems.

Named volumes

A named volume is storage fully managed by Docker itself — you give it a name, Docker decides where it actually lives on the host filesystem, and you never need to know or care about that path. This is the right choice for data a container needs to persist, like a database's files, where you don't need to inspect or edit the raw files from the host directly.

Bash
# Create a named volume explicitly (optional — Docker creates it on first use anyway)
$ docker volume create pgdata

# Run Postgres, storing its data directory in the named volume
$ docker run -d \
    --name db \
    -e POSTGRES_PASSWORD=secret \
    -v pgdata:/var/lib/postgresql/data \
    postgres:16

-v pgdata:/var/lib/postgresql/data mounts the named volume pgdata at that path inside the container. Removing the container (docker rm db) leaves the volume and its data completely intact — a new container can mount the same volume and pick up right where the old one left off:

Bash
$ docker rm -f db
$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:16
# same data is still there
Bash
$ docker volume ls
DRIVER    VOLUME NAME
local     pgdata

$ docker volume inspect pgdata

Bind mounts

A bind mount maps a specific path you choose on the host filesystem directly into the container. This is the right choice during local development, when you want changes to source code on your host to be reflected inside a running container immediately, without rebuilding the image.

Bash
$ docker run -d \
    --name app-dev \
    -v /home/deploy/my-app/src:/app/src \
    -p 3000:3000 \
    my-app:dev

Here, /home/deploy/my-app/src on the host and /app/src inside the container are literally the same files — editing a file on the host is instantly visible inside the running container, which is why bind mounts are so common for local development workflows (and much less common in production, where you want the image itself to be the immutable, deployable artifact).

Named volume Bind mount
Managed by Docker You (a specific host path)
Typical use Persistent application/database data Local development, live-editing source code
Portable across hosts Yes — no host-specific path baked in No — tied to a specific host path
Visible/editable directly on host Indirectly (docker volume inspect shows the path) Directly — it's just a normal host directory

Docker networks

By default, every container Docker creates is attached to a default bridge network, which gives it its own private IP address and lets it reach the outside world (and, with the right setup, other containers) — isolated from the host's own network by default.

For multiple containers that need to talk to each other, the practical approach is a user-defined bridge network, which adds one crucial feature the default bridge doesn't have: automatic DNS resolution by container name.

Bash
# Create a network
$ docker network create app-network

# Run a database on that network
$ docker run -d --name db --network app-network -e POSTGRES_PASSWORD=secret postgres:16

# Run the app on the same network
$ docker run -d --name app --network app-network -p 3000:3000 my-app:1.0

From inside the app container, the database is reachable simply as db — its container name — rather than needing to know any IP address:

Bash
# Inside the app container's environment/config:
DATABASE_URL=postgresql://user:secret@db:5432/mydb

Docker's embedded DNS server resolves db to whatever IP the db container currently has on that network — which matters because container IPs aren't stable across restarts, but container names are.

Bash
$ docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
a1b2c3d4e5f6   app-network    bridge    local
b2c3d4e5f6a7   bridge         bridge    local
c3d4e5f6a7b8   host           host      local
d4e5f6a7b8c9   none           null      local

$ docker network inspect app-network

Containers on different user-defined networks can't reach each other by default unless explicitly connected to both — a useful isolation boundary for, say, keeping a database network separate from a public-facing web tier.

Common mistakes

  • Storing a database's data directory in the container's own writable layer (no volume at all) — the data vanishes the moment the container is removed or recreated.
  • Using a bind mount in production for something meant to be an immutable artifact — it reintroduces "works on my machine" drift, since the container's behavior now depends on whatever happens to be on that host path.
  • Relying on a container's IP address instead of its name for service-to-service communication — the IP can change on restart, while the name (on a user-defined network) resolves consistently via Docker's DNS.
  • Forgetting that the default bridge network does not provide automatic name-based DNS resolution between containers — only user-defined networks do.

Interview questions

Q: What's the difference between a named volume and a bind mount? A named volume is storage fully managed by Docker — you reference it by name, and Docker decides where it physically lives — making it portable and the right choice for persistent data like a database's files. A bind mount maps a specific, chosen path on the host directly into the container, which is ideal for local development (live-editing source code) but ties the container to that host's specific filesystem layout.

Q: Why do containers on a user-defined Docker network get name-based DNS resolution, but containers on the default bridge network don't? Docker's embedded DNS server only registers container names for lookup on user-defined networks — this was a deliberate design choice added after the default bridge network already existed, specifically to make multi-container communication easier without relying on unstable IP addresses. On the default bridge, containers can still reach each other by IP, but not reliably by name.

Q: If you remove a container that was writing to a named volume, what happens to the data? The data in the named volume persists — volumes have an independent lifecycle from any specific container. A new container (even a completely different image) can mount that same named volume afterward and see the exact same data; the volume is only actually deleted if you explicitly run docker volume rm.