Module P-3·25 min read

Bind mounts vs Volumes, managing ephemeral data, and performance implications for databases like PostgreSQL.

Introduction

Containers are designed to be ephemeral. When a container is stopped and removed, everything inside its writable layer (the top layer of the OverlayFS) is permanently destroyed.

If you are running a stateless Next.js frontend or a Node.js API, this is perfectly fine. But what happens when you run a PostgreSQL or Redis container? If the container restarts, you lose your entire database.

In this module, we will explore how to manage stateful data in Docker using Bind Mounts and Volumes, understand their performance implications, and learn when to use which.


The Writable Container Layer (Ephemeral Data)

As we learned in Module 1, when a container starts, Docker creates a thin read-write layer on top of the underlying read-only image layers.

Any files written by your application (e.g., logging a file, uploading an image, writing database rows) are stored in this writable layer.

The critical rule: This layer is tightly coupled to the lifecycle of the container.

  • If the container is stopped (docker stop), the data persists.
  • If the container is removed (docker rm), the data is deleted forever.

To persist data beyond the lifespan of a container, you must bypass the Union File System and store the data directly on the host machine. Docker provides two primary ways to do this: Bind Mounts and Volumes.


1. Bind Mounts

A Bind Mount maps a specific directory on your host machine to a specific directory inside the container.

bash
docker run -v /Users/jatin/my-project/src:/app/src my-node-app

(The syntax is -v HOST_PATH:CONTAINER_PATH)

Characteristics of Bind Mounts

  • Absolute Control: The host machine dictates the exact file path.
  • Two-Way Sync: Changes made on the host are immediately visible in the container, and vice versa.
  • Host Dependent: The container relies on the host having a specific directory structure. This makes docker run commands less portable across different developer machines or production servers.

When to use Bind Mounts

Bind mounts are almost exclusively used for Local Development.

By bind-mounting your local source code into the container, you can use your local IDE (like VS Code) to edit files. Frameworks like Next.js or tools like nodemon will detect the file changes across the mount and instantly hot-reload the application inside the container.

[!WARNING] Performance on macOS/Windows: Bind mounts on Linux are native and incredibly fast. However, on Docker Desktop for macOS and Windows, the files must cross the boundary between your physical host OS and the hidden Linux VM.

If you bind-mount a massive node_modules folder on a Mac, file I/O operations will be severely penalized, and your app will run incredibly slowly. Never bind-mount node_modules—install them natively inside the image instead!


2. Docker Volumes

A Volume is a managed directory created and maintained by Docker itself, stored deep within Docker's internal data directory (e.g., /var/lib/docker/volumes/ on Linux).

bash
# 1. Create the volume docker volume create pgdata # 2. Mount it into the container docker run -v pgdata:/var/lib/postgresql/data postgres:15

Characteristics of Volumes

  • Managed by Docker: You don't care where the data lives on the host disk; Docker handles the pathing.
  • Isolation: The data is isolated from the host machine's normal file system, preventing accidental deletion or permissions issues.
  • High Performance: Because the data lives entirely inside the Linux VM (even on macOS/Windows), I/O performance is native and extremely fast.
  • Lifecycle: Volumes survive container deletion. You must explicitly run docker volume rm pgdata to destroy the data.

When to use Volumes

Volumes are the absolute standard for Production Data and Databases.

If you run PostgreSQL, Redis, or an S3-compatible MinIO container, you should always back their data directories with Docker Volumes.


Managing Permissions

One of the most frustrating aspects of storage in Docker is file permissions, particularly when dealing with Bind Mounts.

If your Node.js application runs as a non-root user (e.g., USER node with UID 1000) inside the container, and it tries to write a file to a bind-mounted directory owned by root on your host machine, the application will crash with an EACCES: permission denied error.

Similarly, if the container writes a file to the bind mount, that file will appear on your host machine owned by whatever UID the container was running as (often root), making it difficult to delete or edit the file locally.

The Solution: Ensure the UID/GID of the process running inside the container matches the UID/GID of the directory owner on the host machine. (We will cover running as non-root deeply in Module 7).


Key Takeaways

  1. Container Layers are Ephemeral: Never store database state or user uploads in the container's writable layer.
  2. Bind Mounts: Used for local development to enable hot-reloading by syncing source code from the host to the container. Suffer from performance penalties on macOS/Windows.
  3. Volumes: Managed by Docker, highly performant, and isolated. The correct choice for persisting database data (Postgres, Redis) in both development and production.
  4. Permissions Matter: Remember that the UID inside the container maps directly to the UID on the host filesystem when using bind mounts.

Knowledge Check

Question 1: You are setting up a local development environment for a Next.js application using Docker. You want to edit files in VS Code and see the changes hot-reload in the browser. Which storage mechanism should you use?

  • A) A Docker Volume, because it has higher performance on macOS.
  • B) A Bind Mount (-v ./src:/app/src), because it maps your local host directory into the container, allowing changes to sync instantly.
  • C) You shouldn't use either; you should rebuild the Docker image every time you save a file.
  • D) The ephemeral writable layer.
Reveal Answer

Correct Answer: B

Bind Mounts are designed for local development. They connect a specific host directory to a container directory, allowing tools like Next.js's hot module replacement (HMR) to watch for file changes originating from your local IDE.


Question 2: You launch a PostgreSQL container in production and need to ensure the database files survive container restarts and updates. Why is using a Docker Volume (docker volume create pgdata) superior to using a Bind Mount (-v /opt/pgdata:/var/lib/postgresql/data) for this specific use case?

  • A) Docker Volumes automatically replicate data to other host machines in the network.
  • B) Docker Volumes encrypt the data at rest by default, whereas Bind Mounts do not.
  • C) Volumes are managed entirely by Docker, isolating the database files from accidental host-level modifications, avoiding complex host pathing dependencies, and ensuring native I/O performance regardless of the host OS.
  • D) PostgreSQL requires root access to write to a Bind Mount, but can write to a Volume as a non-root user.
Reveal Answer

Correct Answer: C

Docker Volumes are the standard for stateful production data. Because Docker manages the storage location internally (usually within /var/lib/docker/volumes/), the data is protected from arbitrary host manipulation. Furthermore, it decoupled the container definition from the specific directory structure of the host machine, making the deployment highly portable across different servers.


Question 3: A Node.js container running as a non-root user (USER node, UID 1000) tries to write a log file to a bind-mounted directory. The directory on the host machine is owned by root. What will happen?

  • A) Docker will automatically elevate the container's privileges temporarily to write the file.
  • B) The application will crash with an EACCES: permission denied error.
  • C) The file will be written successfully, but it will be owned by the node user on the host machine.
  • D) Docker will write the file into the ephemeral container layer instead of the bind mount.
Reveal Answer

Correct Answer: B

Bind mounts do not abstract file permissions; they map them directly. A process running as UID 1000 inside the container operates as UID 1000 on the host filesystem regarding that specific mount. If the host directory is owned by root (UID 0) and doesn't have open write permissions, the Node.js process will be denied access and throw an EACCES error.

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.