You bind-mount a host directory into a Docker container, the process inside writes files there, and afterwards those files on the host are owned by root and your normal user cannot delete them. Explain why this happens and what determines the owner recorded on the host.
answer
- bind mount = same inode, same kernel
- ownership is numeric; names are per-/etc/passwd
- default container user = UID 0 = host root
- --user $(id -u):$(id -g)
- Docker Desktop VM fakes ownership, hides the bug
basics
~20 sA bind mount is a passthrough to the same host inodes; Docker translates nothing. The kernel records the numeric UID of the writing process, and containers run as UID 0 by default, so the host sees root-owned files.
solid answer
~50 sA bind mount is not a copy or a shim: the container's mount namespace sees the **same host inodes**, checked by the **same kernel**. Ownership on Linux is stored numerically, and Docker with user namespaces off (the default) does not remap those numbers. So whatever UID the container process runs as is exactly the UID written into the inode. Unless the image sets `USER` or you pass `--user`, PID 1 in the container is UID 0 — root. Every file it creates in the bind mount is `root:root` on the host, which is why cleaning a `build/` directory afterwards needs `sudo`. The mismatch works the other way too: a container running as UID 1000 reading a host file owned by UID 501 with mode `0600` gets permission denied, because 1000 is not 501 and there are no "other" bits. Fixes: run with `--user "$(id -u):$(id -g)"`, bake a matching UID into the image, chown in an entrypoint before dropping privileges, or write to a named volume instead.
code
bash · 7 linesid -u; id -g
docker run --rm -v "$PWD/out:/out" alpine sh -c 'id; touch /out/from-container'
ls -ln out/
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD/out:/out" \
alpine sh -c 'id; touch /out/as-me'
ls -ln out/go deeper
Be able to state that a bind mount is the same host directory, that containers default to root, and that the fix is --user or a chown. Show you know to compare id on both sides.
Explain that ownership is numeric and per-namespace naming makes it look confusing, and contrast bind-mount behaviour with named volumes. Mention umask and the read-direction failure, not just the write direction.
Talk about which fix you would standardise on and why, the recursive-chown cost on large trees, git dubious-ownership fallout, and how Docker Desktop masks the problem so CI is where it surfaces.
Frame it as a developer-experience and supply-chain concern: whether artifacts should ever be written to bind mounts at all, and whether rootless Docker or userns-remap makes the whole class of bug disappear at the platform level.
## What a bind mount actually is A bind mount takes a directory that already exists on the host and makes it visible at a path inside the container. There is no copy step and no synchronisation daemon: the container's mount namespace gets a second view of the *same inodes on the same filesystem*, and file access is checked by the *same Linux kernel* the host runs. Containers are not virtual machines; they share the host kernel, so there is no place for ownership to be rewritten in transit. ## Ownership is numeric, names are per-view A Linux inode stores an owner as two integers: UID and GID. Names like `alice` or `appuser` do not exist in the filesystem at all — they come from `/etc/passwd` and `/etc/group`, which are resolved *per filesystem view*. The container image ships its own `/etc/passwd`. So the user called `appuser` inside the container might be UID 1000, and on the host UID 1000 might be a different person, or nobody. `ls -l` on each side prints different names for the exact same integer, which is why the problem is confusing before you learn to run `ls -n` and look at raw numbers. ## Why the number is usually zero Unless the Dockerfile has a `USER` instruction or you pass `--user`, the container's main process runs as UID 0. With Docker's default configuration, user namespaces are not enabled for containers, so container UID 0 *is* host UID 0. The container root is constrained by capabilities, seccomp, and AppArmor/SELinux, but its numeric identity on the shared filesystem is plain root. Files it creates in a bind-mounted directory are therefore written as `root:root`, typically mode 0644 or 0755 depending on the process umask. ## The symptoms you actually see - `node_modules/`, `target/`, `dist/`, or a generated `coverage/` directory that the developer cannot delete without `sudo`. - A CI job whose cleanup step fails because the workspace now contains root-owned artifacts and the runner user cannot remove them. - Git complaining about "dubious ownership" or refusing to write index files after a containerised build touched `.git`. - The reverse failure: a hardened image running as UID 10001 gets permission denied reading a mounted config file that is mode 0600 on the host. ## What changes the outcome Only the effective UID/GID of the writing process, plus the umask, plus the permission bits already on the host directory. Concretely: 1. **`--user "$(id -u):$(id -g)"`** — the container process runs with the developer's numeric identity, so new files come out owned by the developer. The numeric UID need not exist in the container's `/etc/passwd`; the process simply has no name, `HOME` may be unset, and tools that call `getpwuid()` may complain. 2. **`USER` with a build-time UID** — `ARG UID=1000` plus `useradd -u $UID` bakes alignment into the image. Reproducible, but wrong the moment someone's host UID differs (macOS staff users are often 501, CI runners 1001). 3. **Entrypoint chown then drop privileges** — start as root, `chown -R` the mount, then `exec gosu appuser "$@"`. Works everywhere, costs a recursive chown on every start, and requires the container to start privileged enough to chown. 4. **Named volumes instead of bind mounts** for anything the container writes and the human never edits. Docker manages ownership inside the volume and no host user is confused by it. 5. **Rootless Docker or `userns-remap`**, where the daemon maps container UIDs into a `/etc/subuid` range so container root lands on the invoking user (rootless) or a dedicated unprivileged range. ## Why some developers never see this On Docker Desktop for macOS and Windows, bind-mounted host paths cross a VM boundary through virtiofs or a FUSE-style share that *fabricates* ownership: files are presented as owned by whichever user is accessing them. The mismatch is hidden, so a team can ship a Compose setup that only breaks when a Linux developer or a Linux CI runner touches it. This asymmetry is the single most common reason "it works on my machine" appears in container permission bugs. ## Diagnosing it fast Run `id` inside the container (`docker exec <c> id`) and `id` on the host, and compare numbers, not names. Then `ls -ln` the mounted path from both sides. If the numbers differ and the mode bits do not grant the container's identity access, you have found the bug — there is nothing subtler going on.
- If you pass --user with a UID that does not exist in the image's /etc/passwd, what breaks?The process runs fine — the kernel only cares about the number — but user lookups fail. `whoami` errors, `HOME` may be unset or `/`, and tools that resolve the current user through getpwuid (ssh, some package managers, some JVM and Node paths) can misbehave or write into `/`. The usual mitigations are setting `HOME` explicitly, using an nss-wrapper style shim, or adding a passwd entry at startup.
- Does running the container as root mean the process is root on the host with full power?With Docker's default settings there is no user-namespace remap, so container UID 0 is numerically host UID 0 for filesystem purposes — which is exactly why files come out root-owned. It is not unrestricted root, though: the default capability set is reduced, seccomp and AppArmor/SELinux profiles apply, and the process is confined to its namespaces. Under rootless Docker or userns-remap, container root maps to an unprivileged host UID instead.
The mount is a second door into the same room, not a photocopy. Whoever walks in leaves their own badge number on everything they touch, and the host reads that badge number with its own directory of names.
saying these in an interview costs you the question
- Claiming Docker "translates" or "maps" ownership on bind mounts by default
- Saying the container has its own kernel or filesystem permission model
- Assuming a matching username inside and outside means matching identity — only the numeric UID counts
- Blanket chmod 777 on the host directory as the standard fix
- Believing macOS behaviour proves the setup is correct on Linux