Skip to main content

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:

  1. Named volumes: Persistent volumes managed by Docker
  2. Host volumes (bind mounts): Directories on the host mounted into containers
  3. 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

yaml
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:

  1. We define a postgres_data named volume at the bottom of the file
  2. 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:

bash
docker volume ls

Output might look like:

DRIVER    VOLUME NAME
local myproject_postgres_data

To get details about a specific volume:

bash
docker volume inspect myproject_postgres_data

Output:

json
[
{
"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

yaml
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:

  1. Edit code on your host machine
  2. See changes immediately reflected in the running container
  3. 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:

yaml
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.

yaml
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:

yaml
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-only
  • driver: Specify which volume driver to use
  • driver_opts: Driver-specific options

Practical Examples

Let's look at some real-world scenarios where Docker Compose volumes shine.

Example 1: WordPress with MySQL

yaml
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:

  1. db_data volume persists the MySQL database
  2. wp_content volume persists WordPress uploads, themes, and plugins
  3. If you stop, start, or even recreate containers, your data remains intact

Example 2: Development Environment

yaml
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:

  1. Mounts source code directories from host to containers
  2. Uses named volumes for node_modules to avoid overwriting them
  3. Allows for real-time code changes while keeping dependencies inside containers

Data Sharing Between Containers

Volumes can be shared across multiple containers:

yaml
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:

  1. The web service writes to a file in the shared volume
  2. The worker service reads from the same file
  3. 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:

bash
docker-compose up

To remove unused volumes:

bash
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:

bash
docker run --rm -v myproject_db_data:/source -v $(pwd):/backup alpine tar -czvf /backup/db_backup.tar.gz -C /source .

This command:

  1. Starts a temporary Alpine container
  2. Mounts the volume to back up
  3. Mounts the current directory as a target for the backup
  4. Uses tar to archive the volume contents

Best Practices for Docker Compose Volumes

  1. 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
  2. Use host volumes during development

    • Mount your source code for immediate feedback
    • Keep build artifacts and dependencies in named volumes
  3. Be careful with volume removal

    • Understand the difference between docker-compose down and docker-compose down -v
    • Consider regular backups for important data
  4. 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
  5. 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:

yaml
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:

  1. Check paths for typos
  2. Ensure the host directory exists
  3. Verify that the path is accessible to Docker

Data Persistence Issues

If data isn't persisting:

  1. Make sure you're not using docker-compose down -v unintentionally
  2. Verify that you're defining the volume correctly
  3. 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:

Exercises

  1. Create a Docker Compose configuration for a simple web application that persists data in a SQLite database using a named volume
  2. Set up a development environment with host volumes for code and named volumes for dependencies
  3. Implement a backup strategy for a multi-container application that uses volumes
  4. 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! :)