Docker Compose Volumes
Introduction
When working with Docker containers, one challenge you'll quickly encounter is data persistence. By default, containers are ephemeral - when a container stops or is removed, all the data inside it disappears. This is where volumes come in.
Volumes provide a way to persist data generated by and used by Docker containers. In Docker Compose, volumes allow you to:
- Share data between multiple containers
- Persist data even when containers are stopped or removed
- Mount host directories into containers for development or configuration
- Improve container performance for I/O-intensive operations
In this tutorial, we'll explore how to use volumes in Docker Compose to effectively manage data persistence in your multi-container applications.
Understanding Volume Types in Docker Compose
Docker Compose supports three types of volumes:
- Named volumes: Persistent volumes managed by Docker
- Host volumes (bind mounts): Directories on the host mounted into containers
- Anonymous volumes: Temporary volumes with a randomly generated name
Let's examine each type and see how to implement them in Docker Compose.
Named Volumes
Named volumes are the preferred way to persist data in Docker. Docker manages these volumes, which means you don't need to worry about their physical location on the host system.
Defining Named Volumes in docker-compose.yml
version: '3'
services:
database:
image: postgres:14
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: example
POSTGRES_USER: user
POSTGRES_DB: myapp
volumes:
postgres_data:
In this example:
- We define a
postgres_data
named volume at the bottom of the file - We mount it inside the
database
service at the path/var/lib/postgresql/data
When you run docker-compose up
, Docker will:
- Create the named volume if it doesn't exist
- Mount it to the specified path in the container
- Persist the data even if you stop or remove the container
Inspecting Named Volumes
To list all volumes:
docker volume ls
Output might look like:
DRIVER VOLUME NAME
local myproject_postgres_data
To get details about a specific volume:
docker volume inspect myproject_postgres_data
Output:
[
{
"CreatedAt": "2023-06-15T14:30:22Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "myproject",
"com.docker.compose.volume": "postgres_data"
},
"Mountpoint": "/var/lib/docker/volumes/myproject_postgres_data/_data",
"Name": "myproject_postgres_data",
"Options": null,
"Scope": "local"
}
]
Host Volumes (Bind Mounts)
Host volumes allow you to mount a directory from your host machine into a container. This is particularly useful during development when you want to see code changes reflected immediately.
Defining Host Volumes in docker-compose.yml
version: '3'
services:
webapp:
image: node:16
working_dir: /app
volumes:
- ./src:/app/src
- ./package.json:/app/package.json
ports:
- "3000:3000"
command: npm start
In this example:
- We mount the local
./src
directory to/app/src
in the container - We also mount the local
package.json
file to/app/package.json
in the container
This setup allows you to:
- Edit code on your host machine
- See changes immediately reflected in the running container
- Keep dependencies and build artifacts separate from your source code
Absolute vs Relative Paths
You can use both absolute and relative paths for host volumes:
volumes:
# Relative path (relative to docker-compose.yml location)
- ./config:/app/config
# Absolute path
- /home/user/projects/myapp/data:/app/data
Anonymous Volumes
Anonymous volumes are temporary volumes with randomly generated names. They're useful when you need a volume but don't care about its name or persistence beyond the lifecycle of the container.
version: '3'
services:
cache:
image: redis:6
volumes:
- /data
In this example, /data
inside the Redis container is mounted to an anonymous volume. The volume will exist as long as at least one container is using it.
Volume Configuration Options
Docker Compose allows you to specify additional options for volumes:
version: '3'
services:
webapp:
image: nginx:latest
volumes:
- type: bind
source: ./nginx.conf
target: /etc/nginx/nginx.conf
read_only: true
volumes:
db_data:
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: '/home/user/data'
Common options include:
read_only
: Mount volumes as read-onlydriver
: Specify which volume driver to usedriver_opts
: Driver-specific options
Practical Examples
Let's look at some real-world scenarios where Docker Compose volumes shine.
Example 1: WordPress with MySQL
version: '3'
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: wp_password
WORDPRESS_DB_NAME: wp_database
volumes:
- wp_content:/var/www/html/wp-content
depends_on:
- db
db:
image: mysql:5.7
environment:
MYSQL_DATABASE: wp_database
MYSQL_USER: wp_user
MYSQL_PASSWORD: wp_password
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db_data:/var/lib/mysql
volumes:
wp_content:
db_data:
In this WordPress example:
db_data
volume persists the MySQL databasewp_content
volume persists WordPress uploads, themes, and plugins- If you stop, start, or even recreate containers, your data remains intact
Example 2: Development Environment
version: '3'
services:
api:
build: ./api
volumes:
- ./api/src:/app/src
- api_node_modules:/app/node_modules
environment:
NODE_ENV: development
command: npm run dev
ports:
- "4000:4000"
frontend:
build: ./frontend
volumes:
- ./frontend/src:/app/src
- frontend_node_modules:/app/node_modules
command: npm run dev
ports:
- "3000:3000"
volumes:
api_node_modules:
frontend_node_modules:
This development setup:
- Mounts source code directories from host to containers
- Uses named volumes for
node_modules
to avoid overwriting them - Allows for real-time code changes while keeping dependencies inside containers
Data Sharing Between Containers
Volumes can be shared across multiple containers:
version: '3'
services:
web:
image: nginx:alpine
volumes:
- shared_data:/shared
command: sh -c "echo 'Hello from web' > /shared/web.txt && nginx -g 'daemon off;'"
worker:
image: alpine
volumes:
- shared_data:/shared
command: sh -c "while true; do cat /shared/web.txt; sleep 5; done"
volumes:
shared_data:
In this example:
- The
web
service writes to a file in the shared volume - The
worker
service reads from the same file - Changes made by one container are immediately visible to the other
Volume Lifecycle Management
Creating and Removing Volumes
Docker Compose creates volumes defined in the volumes
section automatically when you run:
docker-compose up
To remove unused volumes:
docker-compose down -v
Warning: The -v
flag removes all volumes defined in your docker-compose.yml file. Only use it when you want to completely clean up your data.
Backing Up Volume Data
To back up data from a named volume:
docker run --rm -v myproject_db_data:/source -v $(pwd):/backup alpine tar -czvf /backup/db_backup.tar.gz -C /source .
This command:
- Starts a temporary Alpine container
- Mounts the volume to back up
- Mounts the current directory as a target for the backup
- Uses tar to archive the volume contents
Best Practices for Docker Compose Volumes
-
Use named volumes for persistent data
- They're easier to manage and back up than anonymous volumes
- Names provide clarity about the volume's purpose
-
Use host volumes during development
- Mount your source code for immediate feedback
- Keep build artifacts and dependencies in named volumes
-
Be careful with volume removal
- Understand the difference between
docker-compose down
anddocker-compose down -v
- Consider regular backups for important data
- Understand the difference between
-
Document volume usage
- Add comments to your docker-compose.yml explaining what each volume is for
- Include backup and restore procedures in your project README
-
Consider volume drivers for production
- Local volumes are good for development
- For production, consider network-attached storage or cloud storage drivers
Troubleshooting Common Issues
Permission Problems
If you're seeing permission errors:
volumes:
- ./data:/app/data:z
The :z
or :Z
suffixes can help with SELinux permission issues on Linux systems.
Volume Not Mounting
If a volume isn't mounting properly:
- Check paths for typos
- Ensure the host directory exists
- Verify that the path is accessible to Docker
Data Persistence Issues
If data isn't persisting:
- Make sure you're not using
docker-compose down -v
unintentionally - Verify that you're defining the volume correctly
- Check that the container is writing to the correct path
Summary
Docker Compose volumes provide powerful ways to manage data persistence and sharing in your containerized applications:
- Named volumes offer easy persistence managed by Docker
- Host volumes allow direct mounting of host directories for development
- Anonymous volumes provide temporary storage
- Volumes can be shared between containers for data exchange
By properly implementing volumes in your Docker Compose configurations, you can ensure data persists beyond container lifecycles, enable efficient development workflows, and facilitate communication between services.
Additional Resources
To deepen your understanding of Docker Compose volumes:
- Explore the official Docker Compose documentation
- Read about Docker volume drivers
- Practice with the exercises below
Exercises
- Create a Docker Compose configuration for a simple web application that persists data in a SQLite database using a named volume
- Set up a development environment with host volumes for code and named volumes for dependencies
- Implement a backup strategy for a multi-container application that uses volumes
- Create a Docker Compose configuration that demonstrates data sharing between three different containers using the same volume
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)