Skip to content

Container Networking & Volumes

A container, we established, is a process with its own network and mount namespaces. That isolation is the point — but a useful app must talk to clients, talk to a database, and remember data across restarts. This page is the bridge from “isolated process” to “useful service”: how containers connect to the outside world and where their data lives. Both topics circle one decisive idea — containers should be stateless — so we’ll build to that.

Networking: a container has its own private network stack

Section titled “Networking: a container has its own private network stack”

Because of its network namespace, a container gets its own virtual network interface, its own IP, and its own view of ports — separate from the host. By default, a container can reach out to the internet, but the outside world cannot reach in. Its ports are private to its namespace.

Bridge networks: how containers find each other

Section titled “Bridge networks: how containers find each other”

When you create a Docker network, the runtime sets up a bridge — a virtual switch on the host that connects containers. Containers on the same bridge network can talk to each other directly, and Docker runs an embedded DNS so they can reach each other by name, not by IP.

┌──────────────────────── host ────────────────────────┐
│ ┌──────────┐ bridge network "app" │
│ │ web │◄────────────┐ │
│ │ :8080 │ │ (DNS: "db" → db's IP) │
│ └──────────┘ ▼ │
│ ┌──────────┐ │
│ │ db │ reachable as "db" │
│ │ :5432 │ │
│ └──────────┘ │
└───────────────────────────────────────────────────────┘

That name-based addressing matters enormously: the web container connects to db:5432, not to a hard-coded IP. When db restarts and gets a new IP, web doesn’t care — DNS resolves the name afresh. This is service discovery in miniature, and it’s the antidote to the fallacy that topology doesn’t change: in a container world, addresses move constantly, so you address by name.

Terminal window
docker network create app
docker run -d --name db --network app -e POSTGRES_PASSWORD=secret postgres:16
docker run -d --name web --network app -p 8080:80 myweb
# inside "web", the database is simply reachable at host "db"

Publishing ports: punching a hole to the host

Section titled “Publishing ports: punching a hole to the host”

A container’s ports are private until you publish one. -p 8080:80 maps host port 8080 to the container’s port 80 — now traffic arriving at the host’s 8080 is forwarded into the container.

client ──► host:8080 ──[ -p 8080:80 ]──► container:80 ──► app
(public) mapping (private)

The mental model: the left number is the door on the outside of the building (the host); the right number is the door on the office inside (the container). Publishing wires them together. Note EXPOSE in a Dockerfile only documents the port — it’s -p at run time that actually opens it.

Recall from images & layers that a running container writes to a thin, ephemeral layer on top of the read-only image. Here’s the consequence people learn the hard way:

To keep data, you store it outside the container’s lifecycle. Two mechanisms:

  • Volumes — storage managed by the container runtime, living in a dedicated area on the host (or a cloud disk via a plugin). The container mounts the volume at a path; data written there persists independently of the container. Volumes are the preferred way to persist data.
  • Bind mounts — you map a specific host directory straight into the container. Great for local development (mount your source code so edits show up live), but tightly coupled to the host’s filesystem layout, which makes them fragile in production.
Terminal window
# Volume: runtime-managed, portable, the production choice
docker volume create pgdata
docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:16
# Bind mount: host path → container path, great for dev
docker run -v "$PWD/src":/app/src myapp
VolumeBind mount
Where data livesRuntime-managed locationA specific host path you choose
Coupled to host layout?NoYes
Best forProduction persistenceLocal development
Portable across hostsYes (with the right driver)No

The big idea: design containers to be stateless

Section titled “The big idea: design containers to be stateless”

Now the principle everything pointed at. A stateless container holds no important data on its own filesystem — all durable state lives in a volume or, better, an external store (a managed database, object storage like S3, a cache like Redis). The container itself is interchangeable: you can kill it, replace it, run ten copies, and nothing is lost because nothing important lived in it.

Why insist on this? Because it makes every powerful operation safe:

  • Scaling — run 10 identical replicas behind a load balancer; since none owns state, any can serve any request.
  • Restarts & self-healing — an orchestrator can kill and recreate a container freely; state in the external store is untouched.
  • Rolling deploys & rollback — swap the image under the running service without migrating data, because data was never trapped in the old container.
STATEFUL container (fragile) STATELESS container (robust)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ app + DATA │ ✗ kill = data loss │ app │──►│ volume / DB │
└──────────────┘ └──────────────┘ │ (state lives │
can't scale, can't safely restart kill & replace ✓ │ out here) │
scale to N ✓ └──────────────┘

This is the recurring thread of the book, applied to state. The manual, error-prone step statelessness removes is the careful, terrifying dance of “don’t restart that box — the data’s only on its disk and nobody backed it up.” By pushing state to volumes and external stores, restarting, scaling, and redeploying stop being risky manual operations and become routine, automatable, safe ones. That’s how this design makes production safer: it turns the container into something you can throw away and recreate at will, which is exactly what every later automation — orchestration, autoscaling, rolling deploys — assumes you can do.

You can now build an image, ship it through a registry, and run it with a network and persistent storage. The natural next question is: who kills and recreates these containers, restarts the crashed ones, scales them up and down, and keeps the right number running across a whole fleet of machines? No human can do that by hand at production scale. That’s the job of an orchestrator — and it’s Part 4 · Orchestration with Kubernetes.

Container networking and volumes are two halves of one question — how does an isolated process become a useful, survivable service? Step back and answer the five:

  • Why does it exist? Because a container’s network and mount namespaces make it private by default — its IP, ports, and writable layer are walled off — yet a real service must accept clients, find its database, and outlive a restart. Bridge networks, port publishing, and volumes are the controlled doors back out of that isolation.
  • What problem does it solve? Two: addressing in a world where IPs move — embedded DNS lets web reach db:5432 by name, so a restarted database with a new IP just works (service discovery in miniature) — and data that must survive a disposable container, by storing it outside the ephemeral writable layer.
  • What are the trade-offs? Publishing a port (-p 8080:80) isn’t free magic — it programs the host’s kernel firewall with DNAT/MASQUERADE rules, re-introducing the default-deny, open-a-door model. Volumes are portable but runtime-managed and opaque; bind mounts are transparent and great for dev but couple you to a specific host’s filesystem layout, which is fragile in production.
  • When should I avoid it? Avoid bind mounts in production (host-path coupling); avoid storing durable state on the container’s own filesystem ever (the writable layer is deleted with the container); and avoid hard-coded IPs on a bridge network where addresses change constantly.
  • What breaks if I remove it? Without published ports the service is unreachable from outside its namespace; without DNS you’re back to brittle hard-coded IPs; without volumes/external stores your database’s data vanishes on the next restart, redeploy, or reschedule — and statelessness is the precondition every later automation (orchestration, autoscaling, rolling deploys) assumes.
  1. By default a container can reach the internet but the internet can’t reach it. Why, in terms of namespaces — and what does -p 8080:80 change?
  2. On a bridge network, why is connecting to db:5432 better than connecting to a hard-coded IP?
  3. What happens to data written to a container’s own filesystem when the container is removed, and why is that the intended design rather than a bug?
  4. Contrast a volume with a bind mount. Which is preferred for production persistence, and why?
  5. Define a “stateless” container and explain how statelessness makes scaling, self-healing, and rollback safe. Tie it back to the book’s recurring thread.
Show answers
  1. Because of its network namespace, a container has its own private network stack and its ports are private to that namespace — reachable outbound but not inbound by default. -p 8080:80 publishes a port, mapping host port 8080 to the container’s port 80 so outside traffic is forwarded in.
  2. On a bridge network Docker runs an embedded DNS, so db resolves to the database container’s current IP. When db restarts and gets a new IP, web doesn’t care — it resolves the name afresh. A hard-coded IP breaks the moment the container moves. It’s service discovery in miniature.
  3. The container’s writes go to a thin writable layer that is deleted when the container is removed — restart, redeploy, reschedule, or crash all replace the container and lose that data. It’s intended, not a bug: containers are meant to be disposable “cattle,” which is exactly what lets them be killed and recreated freely.
  4. A volume is runtime-managed storage in a dedicated location, portable across hosts and decoupled from the host’s layout; a bind mount maps a specific host directory in and is tightly coupled to that host. Volumes are preferred for production because they’re portable and not tied to one machine’s filesystem (bind mounts shine for local dev).
  5. A stateless container keeps no important data on its own filesystem — durable state lives in a volume or an external store (managed DB, S3, Redis), making the container interchangeable. That makes scaling (any replica can serve any request), self-healing (kill and recreate freely), and rollback (swap the image without migrating data) safe. The thread: it removes the terrifying manual “don’t restart that box, the data’s only on its disk” step, turning restart/scale/redeploy into routine, automatable operations.