KAHIBARO
Discord Login Register

21.8. Volumes

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:

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:

Types of Mounts in Docker

Docker has three main ways to attach storage to containers:

TypeManaged byTypical use case
VolumeDockerPersistent app data, databases, uploads
Bind mountHost userMounting local project code, config files
tmpfs mountKernelFast 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:

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:

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:

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:

bash
docker volume create mydata

Check that it exists:

bash
docker volume ls

Example output:

text
DRIVER    VOLUME NAME
local     mydata
local     pgdata

Inspect a volume:

bash
docker volume inspect mydata

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

bash
docker volume rm mydata

Remove all unused volumes:

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

bash
docker run -v volume_name:container_path image

Example: run PostgreSQL with data in a named volume:

bash
docker volume create pgdata
docker run -d \
  --name my-postgres \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

What happens:

Try:

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

The new container still sees the existing database.

Bind Mounts with `-v`

Syntax:

bash
docker run -v host_path:container_path image

Example: run a Python app from your local code:

bash
docker run --rm -it \
  -v "$PWD":/app \
  -w /app \
  python:3.12 \
  python main.py

Explanation:

Read-only Bind Mount

You can add :ro to mount as read-only:

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

bash
docker run -d \
  --name my-postgres \
  -e POSTGRES_PASSWORD=secret \
  --mount source=pgdata,target=/var/lib/postgresql/data \
  postgres:16

Bind Mount with `--mount`

bash
docker run --rm -it \
  --mount type=bind,source="$PWD",target=/app \
  -w /app \
  python:3.12 \
  python main.py

Table comparison:

StyleExample snippetNotes
-v-v pgdata:/var/lib/postgresql/dataShort, used very often
--mount--mount source=pgdata,target=/var/lib/postgresql/dataMore 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:

If the volume already has data, Docker does not copy anything.

This behavior is useful for:

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:

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:

bash
docker build -t my-fastapi-app .
docker run --rm -it \
  -p 8000:8000 \
  -v "$PWD":/app \
  my-fastapi-app

Now:

You can also use auto-reload tools like:

bash
uvicorn main:app --reload

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

bash
docker run --rm -it \
  -p 8000:8000 \
  -v "$PWD":/app \
  -w /app \
  --env-file .env \
  my-fastapi-app

Postgres:

bash
docker volume create pgdata
docker run -d \
  --name db \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

Reasons:

Volumes in Docker Compose

In real backend projects, you usually use Docker Compose.

Declaring Named Volumes

Example docker-compose.yml:

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

To start everything:

bash
docker compose up

To stop:

bash
docker compose down

To stop and delete the volumes:

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

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

bash
docker volume create pgdata
docker run -d \
  --name db \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

Use case:

To upgrade:

bash
docker stop db
docker rm db
docker run -d \
  --name db \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:17

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

bash
docker volume create staticfiles

Generator container:

bash
docker run --rm \
  -v staticfiles:/app/static \
  my-builder-image

Nginx container:

bash
docker run -d \
  -p 8080:80 \
  -v staticfiles:/usr/share/nginx/html:ro \
  nginx:alpine

Workflow:

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:

bash
docker volume create app-logs
docker run -d \
  --name my-app \
  -v app-logs:/var/log/myapp \
  my-app-image

You can then run a separate container to inspect logs:

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

bash
docker run -d \
  --name db \
  -e POSTGRES_PASSWORD=secret \
  postgres:16

Here:

Always configure the database data directory as a volume.

Watch Permission Issues

On Linux, file permissions come from the host. Common issues:

Example quick fix for a dev-only directory:

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

bash
docker volume ls

Remove a specific volume:

bash
docker volume rm some_old_volume

Prune all unused volumes:

bash
docker volume prune

Use prune carefully to avoid losing important data.

Summary

Volumes are a core part of practical Docker use in backend development:

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

Comments

Please login to add a comment.

Don't have an account? Register now!