Docker & ContainersX Β· Production ArchitectureState
Where state lives β stateful services on a production Docker host
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
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.
| Category | Where it lives | Recovery |
|---|---|---|
| Image layers | /var/lib/docker/overlay2 | Pull again. Free. |
| Container writable layer | Same, per container | Recreate the container. Nothing of value should be here. |
| Volumes and bind mounts | /var/lib/docker/volumes or a host path | Restore from backup, or it is gone. |
| External systems | Another host entirely | Somebody 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.
$ 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.dIllustrative output
Then the volumes nothing is currently using, which is where the unpleasant surprises are:
$ docker volume ls --filter dangling=trueDRIVER VOLUME NAME
local 8f3c1a92b7e0d45f6a1c8e2b4d7f0931a5c6e8b2d4f70193a5c7e9b1d3f5a709
local old-postgres-dataIllustrative 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
tarof 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 volume | Bind mount | |
|---|---|---|
| Path | Docker-managed under /var/lib/docker/volumes | You choose it |
| Which filesystem | Wherever /var/lib/docker is | Any β a dedicated LV or pool |
| Visible to host tooling | Yes, but at an awkward path | Yes, at a path your backup job already knows |
| Ownership and permissions | Initialised from the image | Yours to get right, and easy to get wrong |
| Accidental deletion | docker compose down -v removes it | Survives every Docker command |
| Portability | Docker moves it for you | You 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:
$ docker volume create --driver local --opt type=nfs --opt o=addr=192.0.2.10,rw,nfsvers=4 --opt device=:/export/appdata app-dataapp-dataIllustrative 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.
- Write down which paths on the host are state. This is the inventory above.
- 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.
- On a test host, provision from configuration alone: install Docker, clone the repository, restore the state paths from backup, and bring the stack up.
- Time it. That number is your host-loss recovery time, and it is usually two to five times what people estimate.
- 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
Q1. Which single question best classifies a directory on a Docker host as state?
Q2. Why is a bind mount often preferred over a named volume for irreplaceable production data?
Q3. A rebuild drill on a test host is valuable primarily because it surfaces which of the following? Select all that apply.
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.