Skip to main content
RunBook Academy

Docker & ContainersX Β· Production ArchitectureState

Where state lives β€” stateful services on a production Docker host

Advanced⏱ ~20 min

What you'll learn

  • Classify every piece of data on a Docker host by how it is recovered
  • Decide deliberately whether a stateful service belongs on the host
  • Choose between named volumes and bind mounts for production state
  • Test the claim that a host can be rebuilt without data loss

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-11

Not yet marked complete on this device.

Host design, the edge, segmentation and capacity are all decisions you can revise. Where the data lives is the one that decides how hard every other decision is to revise, because it determines whether a host is a thing you can rebuild or a thing you must recover.

This lesson is not about volume mechanics β€” the storage part of this course covers those. It is about the architectural question they serve: what, on this host, cannot be recreated?

Four kinds of data, and the only distinction that matters

Everything on a Docker host falls into one of four categories, and they differ in exactly one respect: what it takes to get the data back after the disk is gone.

CategoryWhere it livesRecovery
Image layers/var/lib/docker/overlay2Pull again. Free.
Container writable layerSame, per containerRecreate the container. Nothing of value should be here.
Volumes and bind mounts/var/lib/docker/volumes or a host pathRestore from backup, or it is gone.
External systemsAnother host entirelySomebody else’s recovery problem

Only row three is state in the sense that matters. The architectural work is to know exactly what is in it, and to have decided that each item belongs there.

The inventory

Most people discover what is in row three during an incident. Do it now instead β€” the enumeration takes two minutes.

Read-only / Safeevery mount on every running container
$ docker ps --format '{{.Names}}' | xargs -r docker inspect --format '{{.Name}} {{range .Mounts}}{{.Type}}:{{.Source}}->{{.Destination}} {{end}}'
/postgres bind:/srv/data/postgres->/var/lib/postgresql/data
/api volume:/var/lib/docker/volumes/api-uploads/_data->/app/uploads
/redis volume:/var/lib/docker/volumes/redis-data/_data->/data
/proxy bind:/srv/config/nginx->/etc/nginx/conf.d

Illustrative output

Then the volumes nothing is currently using, which is where the unpleasant surprises are:

Read-only / Safevolumes with no container attached
$ docker volume ls --filter dangling=true
DRIVER    VOLUME NAME
local     8f3c1a92b7e0d45f6a1c8e2b4d7f0931a5c6e8b2d4f70193a5c7e9b1d3f5a709
local     old-postgres-data

Illustrative output

Now classify each entry with one question: if this host’s disk died right now, how would I get this back?

  • β€œPull the image again” β†’ not state.
  • β€œIt is in git” β†’ not state; the nginx config bind mount above is configuration, and should be reproducible from a repository.
  • β€œRestore last night’s backup” β†’ state, with a known RPO.
  • β€œI do not know” β†’ state, with no RPO, and this is the finding.

The redis-data volume above is a good example of the ambiguity. Redis used as a cache is not state β€” losing it costs a cold start. Redis used as a queue or a session store is state, and losing it loses work. The volume looks identical in both cases; only you know which it is, and writing that down is the deliverable.

Should the database run on this host at all?

The container-versus-not framing is a distraction. Running PostgreSQL in a container is fine; PostgreSQL does not know or care. The real question is whether the production data should sit on one host’s local disk.

Reasons it is defensible:

  • One deployment mechanism, one monitoring path, one set of resource controls for everything you run.
  • Dev, staging and production share a definition, so β€œworks on my machine” gaps close.
  • For a small system, an external managed database can cost more per month than the entire host.

Reasons to be careful:

  • The failure modes become storage failure modes β€” disk, filesystem, power loss β€” and containers do nothing about those.
  • Backup requires consistency, which requires quiescing or a database-aware dump, not a tar of the volume.
  • A major-version upgrade is coupled to an image tag, and databases are the software least tolerant of an unplanned rollback.
  • Restart semantics are wrong for it. A restart policy that helpfully restarts a database after an unclean shutdown can restart it into recovery repeatedly.

Named volume or bind mount for production state?

Both end up as a directory on the host. The difference is operational, and it matters more than the documentation suggests.

Named volumeBind mount
PathDocker-managed under /var/lib/docker/volumesYou choose it
Which filesystemWherever /var/lib/docker isAny β€” a dedicated LV or pool
Visible to host toolingYes, but at an awkward pathYes, at a path your backup job already knows
Ownership and permissionsInitialised from the imageYours to get right, and easy to get wrong
Accidental deletiondocker compose down -v removes itSurvives every Docker command
PortabilityDocker moves it for youYou move it

For genuinely irreplaceable data on a production host, a bind mount to a path on a dedicated filesystem is usually the better choice β€” not because volumes are worse, but because it puts the data somewhere your existing backup, monitoring and capacity tooling already looks, and on a filesystem you can size and snapshot independently of /var/lib/docker.

services:
  postgres:
    image: postgres:18
    volumes:
      # A dedicated filesystem, in a path the backup job already covers.
      - /srv/data/postgres:/var/lib/postgresql/data

The named-volume equivalent can also point off-host entirely, which is worth knowing about:

Configuration changea volume backed by NFS
$ docker volume create --driver local --opt type=nfs --opt o=addr=192.0.2.10,rw,nfsvers=4 --opt device=:/export/appdata app-data
app-data

Illustrative output

That moves the failure domain to the NFS server, which is a real improvement if the NFS server is better protected than the Docker host, and a real regression if it is not. Moving state does not reduce risk by itself; it relocates it somewhere you have hopefully already solved.

The rebuild test

The property worth designing for is stateless-by-default: every container on this host can be destroyed and recreated from its image and its Compose file, with no data loss.

That is a claim, and claims about recovery are worth exactly as much as the last time they were tested.

  1. Write down which paths on the host are state. This is the inventory above.
  2. Confirm each one is covered by a backup with a known frequency, and that a restore has been performed from it β€” the backup part of this course covers verifying restores.
  3. On a test host, provision from configuration alone: install Docker, clone the repository, restore the state paths from backup, and bring the stack up.
  4. Time it. That number is your host-loss recovery time, and it is usually two to five times what people estimate.
  5. Record everything the drill needed that was not in the repository or the backup. That list is your actual gap.

Knowledge check

Knowledge check Β· 4 questions

  1. Q1. Which single question best classifies a directory on a Docker host as state?

  2. Q2. Why is a bind mount often preferred over a named volume for irreplaceable production data?

  3. Q3. A rebuild drill on a test host is valuable primarily because it surfaces which of the following? Select all that apply.

  4. Q4. Running a database in a container is inherently riskier than running it from a distribution package on the same host.

Passing score: 75%. Answers are checked in this browser.

Where next

The backup and disaster-recovery parts of this course take the state you have just inventoried and cover how to protect and restore it.