21.8. Volumes
Table of Contents
Why Volumes Matter
Containers are temporary by design. If you remove a container, everything inside its filesystem disappears. This is fine for compiled code or temporary files, but not for:
- Databases
- User uploads
- Logs that must survive container restarts
- Configuration files you want to edit without rebuilding the image
Docker volumes solve this by providing persistent storage that lives outside the container lifecycle.
Key rule: Container files are ephemeral.
If you care about the data, put it in a volume.
With volumes you can:
- Keep data when containers are removed or recreated
- Share data between multiple containers
- Separate application code from data
- Move or back up data independently of containers
Types of Mounts in Docker
Docker has three main ways to attach storage to containers:
| Type | Managed by | Typical use case |
|---|---|---|
| Volume | Docker | Persistent app data, databases, uploads |
| Bind mount | Host user | Mounting local project code, config files |
| tmpfs mount | Kernel | Fast in-memory storage, no persistence |
Volumes
A volume is storage that Docker manages. It is created and tracked by Docker, and usually lives under a directory like /var/lib/docker/volumes on the host.
Characteristics:
- Named and managed via Docker CLI
- Independent of specific containers
- Can be attached to many containers
- Preferred for production data
Example idea: database data in /var/lib/postgresql/data inside the container stored in a volume.
Bind Mounts
A bind mount maps a specific host directory or file into the container.
Characteristics:
- You choose the exact host path
- Good for development so you can edit files on the host and see changes inside the container
- Less abstract, more coupled to host system
Example idea: your project source code at /home/user/app on the host mapped to /app inside the container.
tmpfs Mounts
A tmpfs mount lives only in memory.
Characteristics:
- Very fast
- Data is lost when container stops
- Good for sensitive or temporary data
Example idea: caching session data that you never want written to disk.
Creating and Managing Volumes
You usually work with named volumes.
Creating a Volume
To create a volume:
docker volume create mydataCheck that it exists:
docker volume lsExample output:
DRIVER VOLUME NAME
local mydata
local pgdataInspect a volume:
docker volume inspect mydataThis shows details like the mountpoint directory on the host.
Removing a Volume
A volume is not removed automatically when you remove a container. You must remove it explicitly if you do not need it anymore.
Remove a single volume:
docker volume rm mydataRemove all unused volumes:
docker volume prune
Be careful with docker volume prune.
It deletes all volumes that are not currently used by any container.
Using Volumes with `docker run`
You attach volumes to containers using the -v or --mount flags.
Named Volumes with `-v`
Syntax:
docker run -v volume_name:container_path imageExample: run PostgreSQL with data in a named volume:
docker volume create pgdata
docker run -d \
--name my-postgres \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16What happens:
- Docker creates/uses the
pgdatavolume - Inside the container, PostgreSQL writes data to
/var/lib/postgresql/data - The actual bytes are stored on the host in the
pgdatavolume - You can stop and remove the container, then start a new one with the same volume and the data will still be there
Try:
docker stop my-postgres
docker rm my-postgres
docker run -d \
--name my-postgres2 \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16The new container still sees the existing database.
Bind Mounts with `-v`
Syntax:
docker run -v host_path:container_path imageExample: run a Python app from your local code:
docker run --rm -it \
-v "$PWD":/app \
-w /app \
python:3.12 \
python main.pyExplanation:
$PWDis your current directory on the host/appis where the code appears inside the container-w /appsets the working directory inside the container- You can edit files on your host and immediately see the effect when rerunning the container
Read-only Bind Mount
You can add :ro to mount as read-only:
docker run --rm -it \
-v "$PWD":/app:ro \
-w /app \
python:3.12 \
python main.py
Inside the container, /app cannot be modified.
Using `--mount` Syntax
--mount is more explicit and easier to read.
Named Volume with `--mount`
docker run -d \
--name my-postgres \
-e POSTGRES_PASSWORD=secret \
--mount source=pgdata,target=/var/lib/postgresql/data \
postgres:16Bind Mount with `--mount`
docker run --rm -it \
--mount type=bind,source="$PWD",target=/app \
-w /app \
python:3.12 \
python main.pyTable comparison:
| Style | Example snippet | Notes |
|---|---|---|
-v | -v pgdata:/var/lib/postgresql/data | Short, used very often |
--mount | --mount source=pgdata,target=/var/lib/postgresql/data | More explicit, more flags |
How Docker Decides What Goes in a Volume
When you mount a volume to a directory inside the container, Docker checks the image content at that path.
Simple rule:
If you mount an empty volume to a non-empty directory in the image,
Docker copies the image contents into the volume once.
Example:
- Image has default config files in
/etc/myapp - You mount a fresh volume:
-v myconfig:/etc/myapp - Docker copies the initial config files into
myconfig - From then on, changes persist in the volume
If the volume already has data, Docker does not copy anything.
This behavior is useful for:
- Bootstrapping default database schemas
- Shipping default app configuration that can then be customized
Using Volumes in Development
Volumes and bind mounts are extremely useful while developing backend applications.
Mounting Source Code
Instead of rebuilding the image every time you change the code, you can mount your project directory.
Example: FastAPI app with main.py in current directory.
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Run for development:
docker build -t my-fastapi-app .
docker run --rm -it \
-p 8000:8000 \
-v "$PWD":/app \
my-fastapi-appNow:
- You edit
main.pyon your host - Restart the container to see new behavior
- You do not rebuild the image every time you change the code
You can also use auto-reload tools like:
uvicorn main:app --reloadinside the container so code changes apply immediately.
Separate Volumes for Data
You can combine a code bind mount and a named volume for data.
Example: FastAPI app and PostgreSQL container.
FastAPI:
docker run --rm -it \
-p 8000:8000 \
-v "$PWD":/app \
-w /app \
--env-file .env \
my-fastapi-appPostgres:
docker volume create pgdata
docker run -d \
--name db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16Reasons:
- Code: bind mount, easy to change
- Database data: named volume, persistent, independent of code
Volumes in Docker Compose
In real backend projects, you usually use Docker Compose.
Declaring Named Volumes
Example docker-compose.yml:
version: "3.9"
services:
app:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Explanation:
pgdatais declared in thevolumes:section at the bottom- The
dbservice usespgdatato store database data - The
appservice uses a bind mount for the source code
To start everything:
docker compose upTo stop:
docker compose downTo stop and delete the volumes:
docker compose down -v
docker compose down -v removes all volumes defined in the Compose file.
This usually clears your database and other persistent data.
Anonymous Volumes in Compose
You can also mount a container-only anonymous volume by not specifying a name:
services:
app:
image: my-fastapi-app
volumes:
- /app/logs
Here Docker creates a random volume for /app/logs. It persists, but you do not manage the name yourself.
Named volumes are better for important data that you want to back up or share across environments.
Common Patterns and Examples
PostgreSQL Data Volume
Common pattern:
docker volume create pgdata
docker run -d \
--name db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16Use case:
- Restart or upgrade Postgres by changing the image version
- Keep
pgdatavolume so the data is preserved
To upgrade:
docker stop db
docker rm db
docker run -d \
--name db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:17The data is still there, handled by Postgres upgrade procedure if compatible.
Sharing a Volume Between Containers
You can attach the same named volume to multiple containers.
Example: Nginx serving static files generated by another container.
docker volume create staticfilesGenerator container:
docker run --rm \
-v staticfiles:/app/static \
my-builder-imageNginx container:
docker run -d \
-p 8080:80 \
-v staticfiles:/usr/share/nginx/html:ro \
nginx:alpineWorkflow:
- First container writes generated static files to
/app/static - Files are stored in
staticfilesvolume - Nginx reads files from the same volume at
/usr/share/nginx/html
This is a clean way to share build artifacts without copying files manually.
Logs in Volumes
You can store logs in volumes for easier inspection and processing.
Example:
docker volume create app-logs
docker run -d \
--name my-app \
-v app-logs:/var/log/myapp \
my-app-imageYou can then run a separate container to inspect logs:
docker run --rm -it \
-v app-logs:/logs \
alpine \
sh -c "cd /logs && ls && tail -n 50 app.log"Good Practices and Pitfalls
Prefer Volumes for Data, Bind Mounts for Code
A simple rule to follow:
Application data and databases go into volumes.
Application source code and configs during development use bind mounts.
This keeps your data safe and your development workflow flexible.
Do Not Store Important Data Inside the Container Layer
If you write data to a path that is not backed by a volume or bind mount, it lives inside the container's writable layer. Removing the container deletes this layer.
Example mistake:
docker run -d \
--name db \
-e POSTGRES_PASSWORD=secret \
postgres:16Here:
- Data is stored in the container's own filesystem
- If you run
docker rm db, the data is gone
Always configure the database data directory as a volume.
Watch Permission Issues
On Linux, file permissions come from the host. Common issues:
- The container runs as a user that cannot write to the host directory
- Bind mount from a host directory that belongs to a different user
Example quick fix for a dev-only directory:
mkdir data
chmod 777 data
docker run -v "$PWD/data":/var/lib/postgresql/data ...For production, you should match user IDs instead of using permissive permissions, but the concept is the same: the host file permissions govern the mount.
Clean Up Unused Volumes
You can accumulate many unused volumes while experimenting.
List all volumes:
docker volume lsRemove a specific volume:
docker volume rm some_old_volumePrune all unused volumes:
docker volume pruneUse prune carefully to avoid losing important data.
Summary
Volumes are a core part of practical Docker use in backend development:
- Containers are temporary, volumes preserve data
- Named volumes are managed by Docker and are best for persistent data like databases
- Bind mounts are ideal in development to mount your local code and configs
- You can define volumes directly with
docker runor declaratively in Docker Compose - Use volumes to safely store data, share files between containers, and separate code from data
Once you adopt volumes correctly, rebuilding, redeploying, or upgrading containers becomes much safer and easier, because your important data is no longer tied to the life of a container.
Views: 7
KAHIBARO