Skip to main content
ANVISoftware Solutions
Lesson 10 of 16Intermediate16 min

Volumes and Persistence

By the end of this lesson

Keep data alive beyond the container that wrote it.

Everything a container writes goes into its writable layer, and that layer is deleted when the container is removed. For a temporary file that is exactly right. For a database it is a disaster waiting for its first redeployment.

A volume is storage that exists independently of any container. You mount it at a path inside the container, and anything written to that path goes to the volume instead of the writable layer. Remove the container, create a new one with the same volume, and the data is where you left it.

Four ways to attach storage, and what each is for:

Named volume
Storage Docker creates and manages, referred to by a name you chose. The default choice for data a container must not lose, and portable between machines in the sense that the same command works anywhere.
Bind mount
A specific folder on the host appearing inside the container. Tied to that machine's directory layout. Useful for feeding files in during development and for getting files out.
Anonymous volume
A volume with no name, created when you mount a path without naming a source. It works, and you will not be able to tell later which container it belonged to.
tmpfs mount
Memory-backed storage that disappears when the container stops. For scratch data you want to keep off disk entirely.
A database whose data outlives its container
Shell
# Create the volume up front. docker run would also create it on demand
docker volume create employees-db-data

# PostgreSQL writes to /var/lib/postgresql/data, so mount the volume there
docker run -d --name employees-db -p 5432:5432 \
  -v employees-db-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=local-dev-placeholder \
  -e POSTGRES_DB=employees \
  postgres:16

# Replace the container entirely
docker rm -f employees-db
docker run -d --name employees-db -p 5432:5432 \
  -v employees-db-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=local-dev-placeholder \
  -e POSTGRES_DB=employees \
  postgres:16
# The tables are still there

# A bind mount, to hand seed files to a container
docker run --rm -v "$(pwd)/seed:/seed" postgres:16 ls /seed

# Housekeeping
docker volume ls
docker volume inspect employees-db-data
docker volume rm employees-db-data
  • -v name:/path/in/container mounts a named volume. Docker decides where it physically lives; you only ever refer to the name.
  • The container path has to be the one the software genuinely writes to. PostgreSQL uses /var/lib/postgresql/data; SQL Server uses /var/opt/mssql. A volume mounted at the wrong path stays empty while your data continues to die with the container.
  • The second run is the whole point of the lesson. The container was destroyed and recreated; the storage was untouched.
  • The bind mount maps a host folder instead. It is tied to this machine's paths, which is fine for development and a poor fit for anything you want to run identically elsewhere.
  • docker volume rm deletes the data immediately and there is no undo. Docker refuses while a container is using the volume, which is a small safety net rather than a real one.

Named volumes and bind mounts both survive the container. They differ in who is in charge:

 Named volumeBind mount
Where the data livesA location Docker managesA path you specify on the host
Same command on another machineWorks — the volume is created if missingWorks only if that path exists there
Best forDatabase files and anything statefulSource for live reload, seed data, exported reports
Visible in your file explorerNot convenientlyYes, it is an ordinary folder
Main riskDeleting it without realising what was in itHiding files the image put at that path

One boundary worth stating plainly: a volume is persistence, not a backup. It sits on one host, on one disk, and a volume that gets deleted or a disk that fails takes the data with it. Nothing about using Docker changes the need for backups you have restored from at least once.

For local development, exporting from a container to a bind-mounted folder is usually enough. For anything real, use the database's own backup tooling, on a schedule, writing somewhere off that host.

Summary

  • Anything written to a container's own filesystem is deleted with the container
  • A named volume is storage that outlives containers and is referred to by name
  • A bind mount maps a host folder in, which suits development and ties you to that machine
  • The mount path must match where the software actually writes, or the volume stays empty
  • A volume is persistence, not a backup, and stateful workloads still need a backup strategy

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Lose data, then keep it

Start a PostgreSQL container with no volume. Create a table and insert a row. Stop and start the container and confirm the row is still there. Then remove the container, start a fresh one the same way, and look for the table.

Repeat the whole sequence with a named volume mounted at the data directory.

Show solution

Without a volume the table survives stop and start, and disappears when the container is replaced. With a volume it survives both.

Doing the first half is what makes the lesson stick, because the misleading part is the middle step. Stop and start looks like proof that the data is safe, and it is proof of nothing.

Shell
docker run -d --name vol-demo -e POSTGRES_PASSWORD=local-dev-placeholder postgres:16
docker exec -it vol-demo psql -U postgres -c \
  "create table employees (id int, full_name text); insert into employees values (1, 'A. Patel');"
docker restart vol-demo
docker exec -it vol-demo psql -U postgres -c "select * from employees;"
docker rm -f vol-demo

docker run -d --name vol-demo -e POSTGRES_PASSWORD=local-dev-placeholder \
  -v demo-data:/var/lib/postgresql/data postgres:16
# repeat the insert, then remove and recreate with the same -v

Think about it

Think about it

Containers can run a database perfectly well, yet many teams run their API in containers and use a managed database service instead.

What makes the two cases different, given the container technology is the same?

Show solution

Containers are cheapest where replacement is cheap. An API container holds nothing, so it can be killed, moved, scaled and rebuilt freely. A database container holds the one thing you cannot recreate.

Everything difficult about running a database — backups you have tested, failover, version upgrades without data loss, storage that survives a host failure — still has to be done by someone. A managed service is a decision to buy that work rather than build it.

This is not an argument against database containers. For local development and automated tests they are ideal, precisely because throwing the data away is the desired behaviour.

Saved in this browser only.