# Docker Volumes and Bind Mounts: Persist Data in Containers

> Learn Docker volumes, bind mounts and tmpfs: how to persist a PostgreSQL database, back it up, share files with the host and avoid the common permission problems.

- Source: https://www.itwonderlab.com/docker-volumes-bind-mounts/
- Published: 2026-10-06
- Updated: 2026-10-06
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Containers are disposable, data is not

The writable layer of a container is deleted with the container. That is perfect for the application, and terrible for a database. Docker offers three ways to mount storage into a container.

![Docker storage: a named volume managed by Docker, a tmpfs in memory and a bind mount of a host directory are mounted into the container at different paths](https://www.itwonderlab.com/media/tutorials/Diagrams/ITWL-Docker-Volumes.svg "Volumes, tmpfs and bind mounts")

| Type | Where the data lives | Use it for |
|---|---|---|
| **Volume** | A directory managed by Docker, inside the Docker host | Databases and any data that must survive the container. The default choice. |
| **Bind mount** | A directory or file of your computer that you choose | Source code during development and configuration files. |
| **tmpfs** | Memory of the host | Temporary or sensitive files that must never reach the disk. |

## Named volumes

```shell
$ docker volume create pgdata
$ docker run -d --name db \
    -e POSTGRES_PASSWORD=secret \
    -v pgdata:/var/lib/postgresql/data \
    postgres:17
```

`-v pgdata:/var/lib/postgresql/data` mounts the volume at the data directory of PostgreSQL. (Newer major versions of the official PostgreSQL image changed the layout of the data directory: check the "Pitfalls" section of the image page on Docker Hub for the version you use.) If the volume does not exist, Docker creates it.

Prove that the data survives the container:

```shell
$ docker exec -it db psql -U postgres -c "CREATE TABLE t(id int); INSERT INTO t VALUES (1);"
$ docker rm -f db
$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:17
$ docker exec db psql -U postgres -c "SELECT * FROM t;"
 id
----
  1
```

Manage volumes with:

```shell
$ docker volume ls
$ docker volume inspect pgdata
$ docker volume rm pgdata          # fails while a container uses it
$ docker volume prune              # removes the volumes no container uses
```

### The explicit syntax: --mount

`--mount` is more verbose and clearer than `-v`, and it fails instead of silently creating a missing directory:

```shell
$ docker run -d --mount type=volume,src=pgdata,dst=/var/lib/postgresql/data postgres:17
$ docker run -d --mount type=bind,src="$PWD"/conf,dst=/etc/app,readonly my-app
$ docker run -d --mount type=tmpfs,dst=/tmp,tmpfs-size=64m my-app
```

## Bind mounts for development

A bind mount maps a directory of your computer into the container, so editing a file on your editor changes it in the container immediately:

```shell
$ docker run --rm -it -v "$PWD":/app -w /app node:22 npm test
```

For a complete live-reload workflow, see [Docker Compose Watch](https://www.itwonderlab.com/docker-compose-watch/).

> [!WARNING]
> With a bind mount the container can modify your files, and a container running as root creates files owned by root on your disk. Mount read-only (`:ro`) whenever the application does not need to write, and run the container with `--user "$(id -u):$(id -g)"` when it does.

## Back up and restore a volume

A volume is just files, so you back it up by mounting it in a temporary container together with a bind mount of your backup directory:

```shell
# backup
$ docker run --rm -v pgdata:/data:ro -v "$PWD":/backup alpine:3.21 \
    tar czf /backup/pgdata.tgz -C /data .

# restore into a new volume
$ docker volume create pgdata-restored
$ docker run --rm -v pgdata-restored:/data -v "$PWD":/backup alpine:3.21 \
    tar xzf /backup/pgdata.tgz -C /data
```

For a running database it is safer to use its own tool, such as `pg_dump`, because a file copy of a live database may be inconsistent.

## Volumes in Rancher Desktop

Docker, containerd and the volumes live **inside the Linux VM** of [Rancher Desktop](https://www.itwonderlab.com/rancher-desktop/). Named volumes are stored there, which is fast. Bind mounts have to cross from your computer into the VM:

- On **Windows**, files in the Linux (WSL) filesystem are fast, while files under `C:\` cross the Windows boundary and are slower. Keep the projects you bind-mount in the WSL filesystem.
- On **macOS and Linux**, Rancher Desktop offers three mount types in **Preferences > Virtual Machine > Volumes**: *reverse-sshfs* (the default), *9p* (experimental, with options for the cache mode and the security model) and *virtiofs*. If bind mounts are slow or have permission problems, try another mount type.

Only directories shared with the VM can be bind-mounted. By default that includes your home directory.

## Volumes in Compose

In [Docker Compose](https://www.itwonderlab.com/docker-compose-tutorial/) you declare volumes once and use them in several services:

```yaml
services:
  db:
    image: postgres:17
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
```

`docker compose down` keeps the volume. `docker compose down -v` deletes it.

## Next steps

Connect containers with [Docker networking](https://www.itwonderlab.com/docker-networking/).
