My PostgreSQL data outgrew the disk on a Hetzner box. The largest configuration I could get was 320 GB, and the database lived inside a Docker named volume that Docker placed on that same disk. To put the data on storage I controlled, I had to convert the named volume to a bind mount and copy the existing data across.
Both give a container storage that survives the container. The difference is who controls the directory on the host.
Docker volumes are stored within the Docker host’s filesystem and are managed by Docker. Here are some benefits of using Docker volumes:
A bind mount maps a directory or file on the host into the container. Here are some reasons why bind mounts can be advantageous:
For a large database on a disk you pick yourself, the bind mount wins. The cost is that you now own the directory permissions, and Postgres is strict about them.
My PostgreSQL service was configured as follows in my Docker Compose file, with a named volume:
services:
postgres:
image: postgres:latest
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:The first step was to set up the directory that would be used for the bind mount:
mkdir -p /mnt/data/postgresEnsuring the ownership of the directory matches the user and group expected by the PostgreSQL container is crucial. To confirm the correct ownership, I executed the following commands:
docker exec -it [postgres_container_id] /bin/sh
~ ls -al /var/lib/postgresql/
~ id postgresThis output confirmed the user ID and group ID:
uid=999(postgres) gid=999(postgres) groups=999(postgres),101(ssl-cert)Then I changed the user and group ownership of the directory to match Docker volume files.
sudo chown -R 999:999 /mnt/data/postgresAfter setting the permissions, I updated the Docker Compose file to use a bind mount. The named volume declaration at the bottom of the file goes away with it:
services:
postgres:
image: postgres:latest
volumes:
- /mnt/data/postgres:/var/lib/postgresql/dataThe final step involved moving the existing data to the new directory. A one-off Alpine container mounts both the old named volume and the new host path, then copies between them:
docker run --rm -v postgres-data:/from -v /mnt/data/postgres:/to alpine cp -a /from/. /to/.Once the data was successfully transferred, I restarted the services:
docker-compose down
docker-compose up -dAlthough bind mounts require a bit more initial setup and caution, the performance gains and flexibility in managing the data are well worth it. For anyone running into similar storage constraints with Docker, considering bind mounts might provide a suitable solution.
Occasional notes on software, tools, and things I learn. No spam.
Unsubscribe anytime.