Move Docker volume to bind mount

June 12, 2024 · Updated on June 15, 2024

As a longtime user of Hetzner, I've consistently found it to be one of the best hosting solutions available. The performance and features have generally met my needs, except when it comes to storage capacity. Hetzner's maximum configuration offers 320 GB of disk space. For most applications, this is sufficient, but a challenge arises when dealing with larger datasets, such as those used by my PostgreSQL databases.

Docker Volumes vs. Bind Mounts

Before diving into the technical adjustments I had to make, it's essential to understand the difference between Docker volumes and bind mounts.

Docker Volumes:

Docker volumes are stored within the Docker host’s filesystem and are managed by Docker. Here are some benefits of using Docker volumes:

  • Persistence: Even if the Docker container is deleted, the volume persists, which is crucial for non-temporary data.
  • Safety: Volumes are isolated from the core functionality of the host machine, which means there is less risk of a container impacting the host filesystem.
  • Ease of Backup: Docker volumes can be more easily backed up or migrated than bind mounts.

Bind Mounts:

Bind mounts may have a simpler concept where a directory or file on the host is mounted into a container. Here are some reasons why bind mounts can be advantageous:

  • Performance: Accessing data via bind mounts can be faster than with Docker volumes because there is no extra abstraction.
  • Control: Users have direct access to the file system and files, providing more control over the files and directories.
  • Flexibility: They allow for real-time data sharing and updates between the host and container.

Given these points, bind mounts offer a better solution when handling large databases or datasets that require frequent updates and faster access speeds, which is why I decided to make the switch.

Transitioning to Bind Mounts

Initial Configuration

Initially, my PostgreSQL service was configured as follows in my Docker Compose file:

services:
  postgres:
    image: postgres:latest
    volumes:
      - postgres-data:/var/lib/postgresql/data
volumes:
  postgres-data:
 

Creating the directory

The first step was to set up the directory that would be used for the bind mount:

mkdir -p /mnt/data/postgres

Ensuring 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 postgres

This 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/postgres

Modifying Docker Compose

After setting the permissions, I updated the Docker Compose file to use a bind mount:

services:
  postgres:
    image: postgres:latest
    volumes:
      - /mnt/data/postgres:/var/lib/postgresql/data

Moving the Data

The final step involved moving the existing data to the new directory:

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 -d

Wrapping up

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