Omid Sayfun
Omid SayfunComputer Geek
Home
Notebook
Journey

Online

Github
Linkedin
Hevy
Plyatomic
Notebook
The trap of making everything dynamic
March 01, 2024
A try at type-safe groupBy function in TypeScript
April 10, 2025
Email special headers
November 20, 2024
Adding prettier to eslint
April 10, 2025
Storing Vector in Postgres with Drizzle ORM
March 21, 2024
Upgrading my blog to Next 15
April 05, 2025
Canvas macOS issue
February 20, 2024
tsx doesn’t support decorators
March 26, 2025
Validating NestJS env vars with zod
February 06, 2025
Extending Window: types vs interfaces
March 21, 2025
Loading env file into Node process
February 06, 2025
Add update date field to Postgres
February 27, 2024
Using node API for delay
February 06, 2025
React Component Lifecycle
November 28, 2024
How CQRS is different than Event Sourcing
August 18, 2024
RabbitMQ exchange vs queue
August 14, 2024
PgVector similarity search distance functions
August 13, 2024
PgVector indexing options for vector similarity search
July 31, 2024
Using puppeteer executable for GSTS
June 08, 2024
Why EQ is Your Next Career Upgrade
May 13, 2024
Counting GPT tokens
June 30, 2024
Logging route handler responses in Next.js 14
June 19, 2024
Redirect www subdomain with Cloudflare
June 17, 2024
Logging requests in Express app
June 16, 2024
Move Docker volume to bind mount
June 12, 2024
Next.js Hydration Window Issue
May 29, 2024
Using Git rebase without creating chaos in your repo
May 16, 2024
Implementing RPC Calls with RabbitMQ in TypeScript
March 16, 2024
Optimize webpage load with special tags
March 15, 2024
What the hell is Open Graph?
March 13, 2024
My go-to Next.js ESlint config
March 10, 2024
List of useful Chrome args
March 10, 2024
Combining RxJS observables - Part 1
February 20, 2024

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



Continue Reading

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

  • 02-20-2026

    The trap of making everything dynamic

  • 04-11-2025

    A try at type-safe groupBy function in TypeScript

  • 04-10-2025

    Email special headers

  • 04-10-2025

    Adding prettier to eslint

  • 04-09-2025

    Storing Vector in Postgres with Drizzle ORM