Skip to content

Docker Compose Volumes


A container can communicate perfectly well with other containers and still have a serious problem: its data can disappear when the container is removed.

You’ve already learned Docker volumes separately. Now you’ll see how Compose lets you describe those volumes alongside the services that use them, so storage becomes part of the application’s configuration.

Why Use Volumes With Compose?

Consider a database service.

A database stores data inside its container’s writable layer by default. If the container is removed, that data goes with it.

A volume gives the data a lifecycle separate from the container:

              Database Service
                    │
                    ▼
              Database Container
                    │
                    ▼
                db-data
                 volume
                    │
                    ▼
             Data survives
             container removal

With Compose, you can describe this relationship directly:

compose.yaml
     │
     ├── services
     │      └── database
     │
     └── volumes
            └── db-data

The important mental model is:

Your data is gone as soon as container is gone. The volume holds data that should outlive the container.

How Does Compose Use Volumes?

Start with a simple example.

Create a directory:

mkdir compose-volumes
cd compose-volumes

Create compose.yaml:

compose.yaml
services:
  app:
    image: alpine
    command: ["sleep", "infinity"]
    volumes:
      - app-data:/data

volumes:
  app-data:

There are two important parts here.

The service declares that it uses the volume:

services:
  app:
    ...
    volumes:
      - app-data:/data

And the top-level volumes section declares the volume itself:

volumes:
  app-data:

Think of the relationship as:

          Compose Project
                │
        ┌───────┴────────┐
        │                │
      app service     app-data
        │                │
        │            named volume
        │                │
        └───────► /data ◄┘

The volume is mounted at /data inside the container.

What Does The volumes Syntax Mean?

This line:

- app-data:/data

has two sides:

app-data:/data
    │      │
    │      └── path inside the container
    │
    └── volume name

So:

app-data

is the Docker volume, while:

/data

is where that volume appears inside the container.

This is the same volume-mount concept you already learned with Docker. Compose is simply describing it in the Compose file.

How Can You Prove That The Data Survives?

Start the application:

docker compose up -d

Now write some data into the mounted volume.

Use:

docker compose exec app sh

Inside the container, run:

echo "important data" > /data/message.txt
cat /data/message.txt

You should see:

important data

Exit the container shell:

exit

Now remove the Compose application’s container:

docker compose down

At this point, the container is gone.

But the named volume is still present.

Bring the application back:

docker compose up -d

Enter the new container:

docker compose exec app sh

Then:

cat /data/message.txt

You should still see:

important data

This demonstrates the whole idea:

Container A
    │
    │ writes
    ▼
db-data volume
    │
    │ container removed
    ▼
db-data volume still exists
    │
    │ mounted into
    ▼
Container B
    │
    ▼
same data

The container was replaced, but the volume was not.

Note

docker compose down normally removes the containers and Compose-created default networks, but named volumes are not removed by a normal down.

This is important because it prevents a routine application teardown from automatically deleting persistent data.

What Happens If You Run docker compose down?

This distinction is worth seeing clearly.

After:

docker compose down

you have:

Container → removed
Volume    → still exists

Then:

docker compose up -d

creates a new container and mounts the existing volume:

Existing volume
      │
      ▼
New container
      │
      ▼
/data

This is why a volume can preserve data across container replacement.

What If You Really Want To Remove The Volume?

Sometimes you do want to completely delete the application’s data.

Compose provides:

docker compose down -v

The -v option tells Compose to remove the named volumes associated with the application as well.

The lifecycle becomes:

docker compose down
        │
        ├── containers removed
        └── named volumes kept


docker compose down -v
        │
        ├── containers removed
        └── named volumes removed

This distinction is extremely important when working with databases.

Warning

docker compose down -v can permanently delete data stored in the Compose-managed named volumes. Don’t use it casually when working with real application data.

Can Multiple Services Use The Same Volume?

Yes.

For example:

services:
  writer:
    image: alpine
    command: ["sleep", "infinity"]
    volumes:
      - shared-data:/data

  reader:
    image: alpine
    command: ["sleep", "infinity"]
    volumes:
      - shared-data:/data

volumes:
  shared-data:

Now both services have access to the same volume:

                 shared-data
                /           \
               /             \
          writer             reader
             │                 │
           /data             /data

The volume provides shared storage between the containers.

The important point is that the services are still separate containers.

The volume is the shared storage resource.

Warning

This can lead to race condition if multiple containers writes at the same time. Docker won’t protect you — you app should be designed to protect against race condition.

How Are Volumes Different From Networks?

Because you’ve now seen both networks and volumes in Compose, it is useful to keep their purposes separate.

A network answers:

“How can these services communicate?”

A volume answers:

“Where does persistent data live?”

Compare them:

ResourcePurpose
NetworkCommunication between containers
VolumePersistent/shared data

For example:

              Compose Application
                     │
          ┌──────────┴──────────┐
          │                     │
       network                volume
          │                     │
     communication          persistent data
          │                     │
      app ↔ database       database → data

They solve completely different problems. And the biggest difference is:

Networks are deleted with docker compose down but volumes requires extra -v or the long --volumes flag. This is intentional.

When Should You Use A Volume?

Use a volume when data needs to survive container replacement.

Common examples include:

  • database data
  • application uploads
  • files generated by an application that must survive container recreation
  • other state that should not disappear with the container

Don’t add a volume automatically to every service.

If a service is completely stateless, it may not need persistent storage.

The question to ask is:

If this container disappeared tomorrow, would I need this data to still exist?

If the answer is yes, you need to think about persistent storage.

A More Realistic Compose Example

Let’s combine what you’ve learned so far about services, networks, and volumes.

Imagine an application with:

  • a frontend
  • a backend
  • a database

The backend communicates with the database, and the database needs persistent storage.

services:
  frontend:
    image: alpine
    command: ["sleep", "infinity"]
    networks:
      - frontend-network

  backend:
    image: alpine
    command: ["sleep", "infinity"]
    networks:
      - frontend-network
      - backend-network

  database:
    image: alpine
    command: ["sleep", "infinity"]
    networks:
      - backend-network
    volumes:
      - database-data:/data

networks:
  frontend-network:
  backend-network:

volumes:
  database-data: {}

The architecture now contains both communication and storage:

                    frontend-network
                 ┌────────────────────┐
                 │                    │
             frontend ◄───────────► backend
                                      │
                                      │
                                backend-network
                                      │
                                      ▼
                                  database
                                      │
                                      ▼
                              database-data
                                  volume

The relationships are now easy to read from the Compose file:

frontend
   └── frontend-network

backend
   ├── frontend-network
   └── backend-network

database
   ├── backend-network
   └── database-data volume

This is where Compose starts to demonstrate its real value.

The file is becoming a description of the application’s architecture:

  • services describe the components
  • networks describe communication
  • volumes describe persistent storage

Mermaid View Of The Application

The same architecture can be represented visually:

    flowchart LR
    frontend["Frontend"]
    backend["Backend"]
    database["Database"]
    frontend_net(("frontend-network"))
    backend_net(("backend-network"))
    volume[("database-data")]

    frontend --- frontend_net
    backend --- frontend_net
    backend --- backend_net
    database --- backend_net
    database --- volume
  

The network and volume have different roles even though both are declared in the same Compose file.

network → communication
volume  → persistent storage

A Useful Mental Model

At this point, you can think of a Compose application as several layers:

                 compose.yaml
                      │
        ┌─────────────┼─────────────┐
        │             │             │
     services      networks       volumes
        │             │             │
        ▼             ▼             ▼
    containers   communication   persistent
                                storage

Each part answers a different question:

Compose ElementQuestion It Answers
servicesWhat components make up the application?
networksWhich components communicate with each other?
volumesWhich data needs to survive container replacement?

That separation is worth remembering because the next Compose features will add more configuration without changing this basic mental model.

Cleanup

For the examples in this lesson, use:

docker compose down

If you want to completely remove the named volumes created by the examples:

docker compose down -v

Only use the second command when you are sure you no longer need the stored data.

Then return to the parent directory:

cd ..

If you no longer need the example directory:

rm -rf compose-volumes

Warning

docker compose down -v removes the Compose-managed named volumes. rm -rf removes the local example directory. Both are cleanup operations that should be used only when you no longer need the associated data.

What You Should Remember

Compose does not introduce a new kind of storage.

It gives you a way to describe the Docker volumes you already learned about as part of the application’s configuration.

The core relationship is:

service
   │
   │ mounts
   ▼
volume
   │
   └── data survives container replacement

And the most important lifecycle distinction is:

docker compose down
        │
        ├── containers → removed
        └── named volumes → kept

docker compose down -v
        │
        ├── containers → removed
        └── named volumes → removed

The mental model is simple:

Networks define communication. Volumes preserve data. Compose lets you describe both alongside your services.

Next, you’ll move from infrastructure resources to configuration: environment variables and secrets. This is where the same application can be configured differently without changing the Compose file’s structure.

Last updated on