Skip to main content
Self-Hosting & Privacy

Docker Compose Volumes and Backups Explained

I ran docker compose down -v to force a clean restart, out of muscle memory from a different project where that flag was harmless. It wasn't harmless here — it deleted a named volume along with the containers.

milanbuha00August 20, 20266 min read
ShareXin
Docker Compose Volumes and Backups Explained

I ran docker compose down -v to force a clean restart of a stack, out of muscle memory from a different project where that flag was harmless. It wasn't harmless here. The -v deleted the stack's named volume along with the containers, and for about thirty seconds before the restic restore finished, I genuinely didn't know if a year of Vaultwarden entries still existed anywhere.

TL;DR

  • Plain docker compose down removes containers and the default network — named volumes survive.
  • Adding -v also deletes every named volume declared in that compose file. There is no confirmation prompt.
  • Bind mounts map a specific host folder into a container; back them up by pointing a backup tool straight at that folder.
  • Named volumes live under Docker's own storage path; back them up through a helper container, and use a database's own dump tool for live database data, never a raw file copy.
  • The right storage type is a per-service decision, not a project-wide default.

KEY-STAT: 30 — Seconds between running docker compose down -v by mistake and finding out whether the restic restore actually worked

The near-miss: what docker compose down -v actually deletes

Plain docker compose down stops and removes the stack's containers and its default network — anything stored in a named volume is untouched, because Docker doesn't delete storage on a whim. Add -v (or --volumes) and Compose also removes every named volume declared in that compose file, silently and immediately. It doesn't ask which volume, and it doesn't distinguish "I meant it" from "that flag was left over from another terminal history entry." I had a restic backup, and the restore worked — but the thirty seconds between running the command and confirming that made the difference between a story I tell and a genuine data-loss incident.

Bind mounts vs named volumes, in one table

Bind mount Named volume
Storage location A specific folder you choose on the host Managed by Docker under its own storage path
Host visibility Direct — ls the folder any time Indirect — inspect via docker volume commands
Portability across hosts Tied to that exact host path More portable; the app doesn't assume a host layout
Backup method Point any backup tool straight at the folder Needs a helper container, or the app's own export tool
Typical use here Configs, media, anything backed up directly Database engine data (Postgres, MariaDB)

Bind mounts: a host folder as a container's storage

A bind mount is a direct line from a host path to a container path — ./vw-data:/data mounts the vw-data folder sitting next to the compose file straight into the container. Because it's a real, visible folder on the host, any backup tool that already knows how to back up a directory — restic, rsync, a NAS sync job — works on it with zero container-aware logic. That's exactly why configs and media on this homelab live on bind mounts: the backup step doesn't need to know Docker exists at all.

Named volumes: Docker-managed storage

A named volume — declared once under a compose file's top-level volumes: key and referenced by name — lives under Docker's own storage path (/var/lib/docker/volumes/ on Linux) instead of a path you chose. That indirection is a feature for anything that shouldn't assume details about the host: a database engine manages its own file layout inside that volume and doesn't care where Docker physically keeps it. The tradeoff is that backing it up isn't a plain folder copy — it needs either a helper container or the database's own export tool.

This homelab's actual storage choice, service by service

Service Storage type Why
Vaultwarden (/data) Bind mount Backup target for restic needs to be a plain folder, not a Docker-managed path
Caddy config and certs Bind mount Edited by hand often enough that direct host access matters
Jellyfin media library Bind mount to existing NAS storage The Raspberry Pi NAS already owns that storage; Jellyfin just points at it
Postgres data directory Named volume Database files should not be raw-copied live; back it up with pg_dump, not a folder copy

Full setup detail for the Vaultwarden bind-mount layout is in the Vaultwarden self-hosting guide.

Backing up a bind mount

No container involvement needed — point the backup tool straight at the folder:

restic backup ./vw-data --tag vaultwarden

Backing up a named volume correctly

For a named volume holding plain files (not a live database), a short-lived helper container mounts both the volume and a host folder, then archives it:

docker run --rm \
  -v vw_internal_data:/source:ro \
  -v "$(pwd)/backup":/backup \
  alpine tar czf /backup/volume-backup.tar.gz -C /source .

Warning

That pattern is fine for static files, but a raw tar of a live database's named volume can capture it mid-write and produce a backup that looks fine and fails to restore. For Postgres or MariaDB, run the engine's own dump tool against the running container — docker compose exec postgres pg_dump -U user dbname > backup.sql — never a file-level copy of a live database's volume.

Tip

Whichever method you use, restore it into a throwaway location and check the data is actually there at least once. A backup that has never been restored is a hope, not a backup — that's exactly the check that mattered during the down -v near-miss above.

Decision guide: bind mount or named volume?

Service type Recommendation Why
Config files, certs, anything hand-edited Bind mount Direct host access; plain-folder backup
Media libraries and existing NAS storage Bind mount Points at storage that already exists outside Docker
Database engine data (Postgres, MariaDB, Redis persistence) Named volume + the engine's own dump tool Avoids raw-copy corruption risk on live data
App data with no live-write concerns (static app state) Either — bind mount if you want direct visibility Named volume is fine too; the risk is specifically live databases

Storage choice is independent of the service's restart policy — see the restart policies guide — and independent of which network it joins, covered in Docker Compose networking. All three are separate decisions made per service, not one global default for the whole stack.

The bottom line

Put configs and media on bind mounts so any generic backup tool can reach them directly. Put live database data on named volumes and back it up with the database's own tool, never a raw copy. Then actually restore a backup once, before docker compose down -v becomes the thing that tests your backup strategy for you.

Frequently asked questions

Does docker compose down delete volumes?

No, not by default. Plain docker compose down removes containers and the default network but leaves named volumes intact. Only adding the -v (or --volumes) flag deletes the named volumes declared in that compose file.

What is the difference between a bind mount and a named volume?

A bind mount maps a specific folder you chose on the host into the container, so you can browse and back it up directly. A named volume is storage Docker manages itself under its own path, better isolated from host-path assumptions but requiring a helper container or the app's own tool to back up.

How do you back up a Docker named volume?

Run a short-lived container that mounts the named volume read-only alongside a host backup folder, then archive the volume's contents with tar. For a live database, use the database engine's own export tool (pg_dump, mysqldump) instead of a raw file copy.

Is it safe to copy a database's Docker volume directly for backup?

Not while the database is running. A raw copy of a live database's files can capture it mid-write, producing a backup that appears complete but fails to restore cleanly. Use the database engine's own dump tool against the running container instead.

Related stories

More from Self-Hosting & Privacy

Stay in the loop

Get the latest articles delivered to your inbox. No spam, unsubscribe anytime.

Read next

Self-Hosted Apps: What's Actually Worth It

Every self-hosted apps roundup is an unvetted list with no honest 'skip this one.' Six categories actually tested on a homelab — AI chatbot, music, automation, Reddit, Git, and CI/CD — with real hardware cost and one category that isn't worth it for most people.

Continue Reading