Skip to main content

Docker Compose Volumes: A Practical Guide (2026)

Volumes are how a Docker Compose service keeps data after the container is removed. Without a volume, everything written inside a container is gone the moment you run docker compose down. This guide covers the two volume types, the exact syntax, and the things that trip people up.

The two kinds of volume

TypeWhere data livesUse it for
Named volumeManaged by DockerDatabases, app data you don't edit by hand
Bind mountA path on your hostConfig files, source code, anything you edit

Named volume

Docker creates and manages the storage. You refer to it by name.

services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data

volumes:
pgdata:

The top-level volumes: block declares the named volume. The service mounts it at the container path. Data in pgdata survives docker compose down and is reused on the next up.

Bind mount

You map a host path directly into the container.

services:
web:
image: nginx
volumes:
- ./site:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/nginx.conf:ro

./site is relative to the docker-compose.yml file. The :ro flag mounts the second file read-only. Bind mounts are best when you want to edit files on the host and have them show up live in the container.


Volume syntax: short vs long form

The short form is source:target:options:

volumes:
- pgdata:/var/lib/postgresql/data
- ./config:/app/config:ro

The long form is more explicit and easier to read for complex setups:

services:
db:
image: postgres:16
volumes:
- type: volume
source: pgdata
target: /var/lib/postgresql/data
- type: bind
source: ./init.sql
target: /docker-entrypoint-initdb.d/init.sql
read_only: true

volumes:
pgdata:

Both do the same thing. Use long form when you want clarity or extra options like read_only.


Sharing a volume between services

Mount the same named volume in more than one service to share data:

services:
app:
build: .
volumes:
- uploads:/app/uploads
worker:
build: .
volumes:
- uploads:/app/uploads

volumes:
uploads:

Both app and worker now read and write the same uploads storage.


Inspecting and removing volumes

# List volumes
docker volume ls

# See where a named volume lives and its details
docker volume inspect <project>_pgdata

# Remove volumes when you bring the stack down
docker compose down -v

Warning: docker compose down -v deletes named volumes for the stack. That removes your database data. Leave off -v if you want to keep it.


Backing up a named volume

Named volumes are managed by Docker, so back them up by copying their contents through a throwaway container:

docker run --rm \
-v <project>_pgdata:/data \
-v $(pwd):/backup \
alpine tar czf /backup/pgdata-backup.tar.gz -C /data .

Restore by extracting the archive back into the volume the same way.


Common mistakes

  • Forgetting the top-level volumes: block. A named volume used in a service must also be declared at the bottom of the file.
  • Expecting bind-mount edits without :ro to be safe. A read-write bind mount lets the container modify your host files. Use :ro for config.
  • Using down -v out of habit. It wipes your data. For a normal stop, docker compose down (no -v) is what you want.
  • Relative paths from the wrong directory. Bind-mount paths are relative to the Compose file, not your shell's working directory.

For a refresher on how Compose relates to plain Docker, see Docker vs Docker Compose. To map ports alongside your volumes, see Docker Compose ports.


Skip the volume management

On Hostim.dev, persistent storage is attached to your app declaratively — no host paths, no manual backups of a named volume.

👉 Deploy an app with persistent storage built in

Frequently asked questions

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

A named volume is storage managed by Docker and referenced by name — best for databases and app data you don't edit by hand. A bind mount maps a specific folder on your host into the container — best for config files and source code you want to edit live. Named volumes are more portable; bind mounts give you direct host access.

How do I persist data in Docker Compose?

Add a volume to the service and, for named volumes, declare it in the top-level volumes block. For example, mount 'pgdata:/var/lib/postgresql/data' on a Postgres service and list 'pgdata:' under volumes. The data then survives 'docker compose down' and is reused on the next 'up'.

Does docker compose down delete volumes?

Plain 'docker compose down' does not delete named volumes — your data is kept. Adding the '-v' flag ('docker compose down -v') removes the named volumes for the stack, which deletes that data. Use '-v' only when you intend to wipe the data.

How do I share a volume between two services in Docker Compose?

Declare one named volume in the top-level volumes block and mount it in each service at the path you want. Both services then read and write the same storage. This is common for an app and a background worker that share an uploads folder.