Bind Mounts vs Volumes in Docker: Which One Should You Use

by Eric Hanson, Backend Developer at Clean Systems Consulting

The wrong choice that compounds over time

Your team uses bind mounts for everything: source code, database data, config files, log output. It works locally. Then a developer joins from Windows, and the database volume has permission errors. CI runs slower because bind mount performance on Docker Desktop is half of native. The database volume ends up in the project directory, gets accidentally committed, or worse — gets deleted when someone cleans up the repo.

The choice between bind mounts and named volumes isn't about preference — they're designed for different use cases. Using one where the other belongs creates problems that are annoying to diagnose because the symptom (permissions error, slow build, missing data) doesn't obviously point to the storage type as the cause.

What each one actually is

Bind mount: A specific path on the host filesystem is mounted into the container. You control where on the host the data lives.

docker run -v /absolute/host/path:/container/path image
# or relative in Compose:
# - ./relative/path:/container/path

Named volume: Docker creates and manages a storage area. You refer to it by name. Docker controls where on the host it actually lives.

docker run -v my-volume-name:/container/path image

Both appear inside the container the same way — as a mounted directory. The differences are operational.

Use bind mounts for: source code and config files you edit

Bind mounts are the right tool when you want changes on the host to immediately appear inside the container. The canonical use case is development source code:

services:
  app:
    build: .
    volumes:
      - ./src:/app/src      # changes here appear in the running container
    command: npm run dev     # file watcher picks up changes

Without the bind mount, you'd rebuild the image on every source change. With it, the file watcher inside the container sees edits in real time.

Config files you need to tune frequently also fit here:

services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

:ro (read-only) prevents the container from modifying the config file, which is appropriate when the config is source-controlled and should only be changed intentionally.

When bind mounts make sense:

  • Development source code (live reload)
  • Config files managed in the repository
  • Files you need to access from both host and container regularly

Use named volumes for: persistent application data

Named volumes are the right tool for data that needs to survive container lifecycle events but doesn't need to be directly managed by the user on the host filesystem.

services:
  db:
    image: postgres:16-alpine
    volumes:
      - pg_data:/var/lib/postgresql/data

volumes:
  pg_data:

Why named volumes for database data:

  • Docker manages creation: no need to create a directory with the right permissions before running
  • Permissions are correct: Docker initializes the volume, PostgreSQL initializes the data directory, no UID/GID mismatch
  • Not exposed to the host: nobody accidentally deletes it, commits it, or runs disk cleanup scripts that remove it
  • Performance: on Docker Desktop (Mac/Windows), named volumes use the VM's local filesystem directly, which is significantly faster than bind mounts that must cross the host-VM boundary

The performance difference is meaningful: on an M2 Mac with Docker Desktop, a PostgreSQL container with a bind mount to the host runs at roughly 20–40% of the speed of the same container with a named volume. For database-heavy integration tests, this is noticeable.

When named volumes make sense:

  • Database storage (PostgreSQL, MySQL, MongoDB)
  • Application state that persists across container recreations
  • Cache directories that should survive restarts but don't need host-side access
  • Any data that Docker should manage rather than the user

The node_modules pattern: named volume for a specific path

A common hybrid pattern: bind mount the entire project, but use a named volume for node_modules to prevent the host's node_modules from shadowing the container's:

services:
  app:
    build: .
    volumes:
      - .:/app                        # bind mount — source code on host
      - node_modules:/app/node_modules  # named volume — container-managed

Docker evaluates more specific mount paths last, so the named volume at /app/node_modules takes precedence over the bind mount at /app for that path. The container uses its own npm-installed node_modules, while the host's version is ignored.

Without the named volume for node_modules, the host's version (which may be absent, may have been installed for the wrong OS, or may be a different version) is mounted over the container's. The app fails with missing module errors, or with errors about binaries compiled for the wrong platform.

Permission problems: bind mounts

The most common bind mount problem: the host path is owned by uid:gid X, but the container process runs as uid:gid Y.

On Linux, UIDs are universal — if your host user is UID 1000 and the container process is UID 1001, the container process can't write to files owned by UID 1000. On Docker Desktop (Mac/Windows), the VM layer handles UID mapping and this is less commonly an issue.

Diagnosis:

# Check host directory ownership
ls -la ./your-bind-mount-path

# Check what UID the container process runs as
docker exec -it container-name id

Fix options:

  1. Set the container's user to match the host UID: --user $(id -u):$(id -g)
  2. Change the host directory ownership: chown -R 1001:1001 ./your-path
  3. Use a named volume instead, and copy data in/out as needed

For development bind mounts, option 1 (run container as the host user) is simplest:

services:
  app:
    user: "${UID}:${GID}"   # from shell environment

Tmpfs: the third option nobody mentions

For data that needs to be writable but doesn't need to survive even a container restart:

services:
  app:
    tmpfs:
      - /tmp
      - /app/cache

tmpfs mounts are in-memory, fast, and automatically cleared when the container stops. Use them for:

  • Temp files that applications write and immediately read
  • Test databases when you want a truly fresh state each run
  • Any path your app writes to but doesn't need persisted

In a Kubernetes pod:

volumes:
  - name: tmp
    emptyDir:
      medium: Memory

The decision rule

Use caseTool
Source code during developmentBind mount
Config files from the repoBind mount (:ro)
Database dataNamed volume
Application stateNamed volume
node_modules or similar build artifactsNamed volume (specific path)
Temp files, caches that resettmpfs
Data you need to access from host toolingBind mount (with UID consideration)

If you're using bind mounts for database storage, switch to named volumes. If you're using named volumes for development source code and wondering why live reload doesn't work, switch to bind mounts. The distinction maps cleanly to what each mechanism is designed to do.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

The Best Ways to Organize Your Freelance Workflow

Freelancing can feel like juggling a dozen balls while riding a unicycle. With the right workflow, you can keep everything moving smoothly—and stay sane.

Read more

Your Docker Compose File Is Messier Than It Needs to Be

Docker Compose files accumulate complexity as projects grow — hardcoded values, duplicated configuration, missing health checks, and environment-specific hacks that never get cleaned up. A structured approach keeps them maintainable.

Read more

Your Docker Image Has More Inside It Than You Think

Most developers have a rough sense of what's in their Docker image, but very few have actually inspected it. What you find when you look closely — sensitive files, debug tools, unused binaries — is usually a surprise.

Read more

Hibernate Schema Generation and Validation — What ddl-auto Actually Does in Production

The spring.jpa.hibernate.ddl-auto setting controls whether Hibernate modifies your database schema at startup. Most teams use create or update in development and then wonder why production behaves differently. Here is what each setting does and what belongs in production.

Read more