Bind Mounts in Development
A bind mount maps a host directory straight into the container, so a file you save on the host is instantly the file the container runs — no rebuild, no copy. This is what makes containerized development bearable: edit Driftwood's Python source in your editor, and the gunicorn reloader inside the container picks it up. The same directness that makes it good in development is what makes it a footgun — most infamously when the mount clobbers a directory the image already populated.
This page is about using bind mounts where they belong — the inner development loop — and about the three ways they bite: hidden dependencies, accidental writes, and UID mismatches. Each is a predictable consequence of what a bind mount actually does, which is replace the container's view of a path with the host's.
Live-Editing Source on the Host
Bind-mount the project directory over the app's working directory — -v /home/dev/driftwood:/app — and host edits appear in the container immediately. Pair that with a hot-reloader (gunicorn's --reload, a framework's dev server) and you get an edit-save-refresh loop with no image rebuild between changes. The container holds the runtime and the installed dependencies from the image; the bind mount supplies the source you're actively editing. That split is the whole appeal — you iterate on code at host speed while the container provides the environment.
The Clobber Footgun
A bind mount does not merge with what the image put at that path — it replaces the container's view of it entirely. Mount your host source over /app and the container now sees your host directory there, and only your host directory. If the image had installed something into /app at build time, it's gone from view. The classic break: a Python image ran pip install into a site-packages or .venv under the working directory, or a Node image baked node_modules into /app/node_modules — bind-mount your host source over /app and those installed dependencies vanish, because your host tree doesn't contain them. The container starts and then dies with "module not found."
/app — a file you save on the host is instantly the file the container runs, so edits reflect instantly with no rebuild.node_modules or site-packages vanish.Working Around the Clobber
There are two standard fixes. The clean one is to keep dependencies out of the path the bind mount covers — install a Python virtualenv at /opt/venv instead of inside /app, so mounting source over /app never touches the packages. The other is to shadow the dependency subdirectory with an anonymous volume layered on top of the bind mount — -v /home/dev/driftwood:/app -v /app/node_modules — so the image's node_modules survives under the host mount because the anonymous volume re-covers that one subpath.
# Python — venv lives outside the mounted path, so it's never clobbered docker run -d --name driftwood-web \ -v /home/dev/driftwood:/app \ -e PATH=/opt/venv/bin:$PATH \ driftwood:dev # image installed deps into /opt/venv, not /app # Node — anonymous volume re-covers node_modules under the host mount docker run -d --name driftwood-web \ -v /home/dev/driftwood:/app \ -v /app/node_modules \ driftwood:dev
In the Python case the host source at /app and the dependencies at /opt/venv never overlap, so there's nothing to clobber. In the Node case the second mount tells Docker to put an anonymous volume at /app/node_modules, which sits on top of the bind mount and preserves the image's installed modules at that one subpath while the rest of /app reflects your host edits.
Read-Only Mounts
Append :ro when the container should read a file but never write it. Driftwood's proxy takes its nginx config exactly this way — -v /home/dev/driftwood/nginx.conf:/etc/nginx/conf.d/default.conf:ro — so the container reads the config at startup and cannot corrupt the host's source-of-truth copy. Read-only is both a safety rail and a statement of intent: it documents that this mount is input to the container, not a place it writes back to. For any config or static asset the container only consumes, :ro should be the default.
Ownership and UID Mismatch
A bind-mounted file carries its host UID, not a username — the kernel only stores the numeric owner. If the container's process runs as a different UID than the host file's owner, it hits permission-denied on files it appears to "own" by name. The sharpest version is the reverse: the container runs as root (UID 0), writes files into the bind-mounted directory, and now the host directory is littered with root-owned files the developer's own account can't edit or delete without sudo. The fix is to align UIDs — run the container as a user matching the host owner, or set ownership deliberately — so the host directory stays manageable from both sides.
- Bind-mounting host source over the image's working directory and wiping the dependencies the image installed there — the container starts but fails on import because
node_modulesorsite-packagesis now the empty host view. - Using a bind mount for Driftwood's database in development "to see the files" and inheriting host ownership and filesystem-specific behavior that diverges from the named-volume production setup, hiding bugs until deploy.
- Mounting a config file read-write when read-only was intended, so a misbehaving container can overwrite or truncate the host's source-of-truth config.
- Running the container as root with a bind mount and littering the host directory with root-owned files the developer's own account can't edit or delete without
sudo. - Bind-mounting on Docker Desktop and blaming the app for slow I/O — bind-mount reads and writes cross the macOS/Windows-to-Linux-VM boundary (topic 02, topic 06), which is the latency, not the code.
- Use bind mounts for source in development and named volumes for data in production, and keep the two setups close enough that dev still catches real bugs.
- Install dependencies into a path your source bind mount doesn't cover, or shadow the dependency directory with an anonymous volume, so a host mount never hides the image's installed packages.
- Mark every bind mount the container only needs to read with
:ro, including Driftwood's nginx config, so the container can't mutate host files. - Align UIDs — run the container as a user matching the host owner, or set ownership deliberately — so bind-mounted files don't become root-owned and unmanageable on the host.
:z/:Z) that matter on Fedora/RHEL hosts
Kubernetes a host bind is a hostPath volume, explicitly discouraged for portability (Ch12 topic 76)
NFS/Samba share · rsync-on-save the pre-container sync workflow a bind mount replaces
Knowledge Check
Why does bind-mounting host source over the image's working directory break the container with "module not found"?
- A bind mount replaces the container's view of that path, hiding the dependencies the image installed there
- It permanently deletes the installed dependencies from the image's read-only layers, so they are gone for every container
- It merges the host and image directories into one tree, and the merge quietly corrupts the installed dependency files
- It mounts the path read-only, so the language runtime is blocked from loading any of the modules
What does layering an anonymous volume at /app/node_modules over a source bind mount accomplish?
- It re-covers that one subpath so the image's installed node_modules survives under the host mount
- It copies the host's own
node_modulesdirectory into the container at startup and keeps the two trees synced - It makes the entire bind mount read-only so nothing can overwrite the dependencies the image installed there
- It reinstalls all of the dependencies from scratch on every container start, before the app process runs
When is a bind mount the right tool, and when is it the wrong one?
- Right for live-editing source or sharing a host config in dev; wrong for production database data, where a named volume belongs
- Right for the production database's data directory because a fixed host path is easy to back up, wrong for live-editing source in development
- Right for keeping a decrypted secret off disk entirely, wrong for any on-disk source file you edit by hand in the editor
- Right whenever you want one mount definition to stay portable across many different hosts and CI runners alike
Why does a bind mount with the container running as root leave files the host developer can't delete?
- The container writes files owned by root's UID, and a non-root host user can't modify UID-0 files without sudo
- The files are locked inside the container's mount namespace and never appear on the host disk, so they vanish when it stops
- Docker encrypts every file written through a bind mount, so that only the root user on the host can later read them back
- The kernel stores the owner as a plain username string that the host account database then can't resolve to a local user
You got correct