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 removalWith Compose, you can describe this relationship directly:
compose.yaml
│
├── services
│ └── database
│
└── volumes
└── db-dataThe 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-volumesCreate 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:/dataAnd 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:/datahas two sides:
app-data:/data
│ │
│ └── path inside the container
│
└── volume nameSo:
app-datais the Docker volume, while:
/datais 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 -dNow write some data into the mounted volume.
Use:
docker compose exec app shInside the container, run:
echo "important data" > /data/message.txt
cat /data/message.txtYou should see:
important dataExit the container shell:
exitNow remove the Compose application’s container:
docker compose downAt this point, the container is gone.
But the named volume is still present.
Bring the application back:
docker compose up -dEnter the new container:
docker compose exec app shThen:
cat /data/message.txtYou should still see:
important dataThis demonstrates the whole idea:
Container A
│
│ writes
▼
db-data volume
│
│ container removed
▼
db-data volume still exists
│
│ mounted into
▼
Container B
│
▼
same dataThe 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 downyou have:
Container → removed
Volume → still existsThen:
docker compose up -dcreates a new container and mounts the existing volume:
Existing volume
│
▼
New container
│
▼
/dataThis 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 -vThe -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 removedThis 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 /dataThe 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:
| Resource | Purpose |
|---|---|
| Network | Communication between containers |
| Volume | Persistent/shared data |
For example:
Compose Application
│
┌──────────┴──────────┐
│ │
network volume
│ │
communication persistent data
│ │
app ↔ database database → dataThey solve completely different problems. And the biggest difference is:
Networks are deleted with
docker compose downbut volumes requires extra-vor the long--volumesflag. 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
volumeThe relationships are now easy to read from the Compose file:
frontend
└── frontend-network
backend
├── frontend-network
└── backend-network
database
├── backend-network
└── database-data volumeThis 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 storageA Useful Mental Model
At this point, you can think of a Compose application as several layers:
compose.yaml
│
┌─────────────┼─────────────┐
│ │ │
services networks volumes
│ │ │
▼ ▼ ▼
containers communication persistent
storageEach part answers a different question:
| Compose Element | Question It Answers |
|---|---|
services | What components make up the application? |
networks | Which components communicate with each other? |
volumes | Which 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 downIf you want to completely remove the named volumes created by the examples:
docker compose down -vOnly 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-volumesWarning
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 replacementAnd the most important lifecycle distinction is:
docker compose down
│
├── containers → removed
└── named volumes → kept
docker compose down -v
│
├── containers → removed
└── named volumes → removedThe 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.