Bind Mounts, Read-Only Mounts, tmpfs, Mount Propagation, Subpaths, SELinux Labels, and Host Coupling: Diagnostics, Failure Modes, Security, and Performance
Diagnose mount failures and host-side effects without broad permissions, security disablement, or destructive cleanup, preserving first-failure evidence.
Learning objectives
- Use an evidence-first sequence to separate source-path, context, permission, SELinux, propagation, and application failures.
- Diagnose a missing bind source without replacing the error with silent directory creation.
- Explain why mounting broad host paths or Docker control interfaces into untrusted containers is a host-compromise risk.
- Diagnose root-owned development files and remote-context path confusion using numeric identity and daemon-host evidence.
- Preserve security controls and apply the least destructive correction.
--mount to
-v. Each of those can hide the real layer that failed.
1. Evidence-first diagnostic sequence
| Step | Question | Evidence |
|---|---|---|
| 1. Context | Which daemon is receiving this request? | docker context show/inspect |
| 2. Source | Does the exact source exist on that daemon host? | Host/remote evidence; preserved mount error |
| 3. Mount object | What did Docker configure? | docker inspect ... .Mounts |
| 4. Identity | Which UID/GID needs access? | id, numeric ls -ln |
| 5. LSM | Is SELinux/AppArmor denying access? | Host security status/audit evidence |
| 6. Platform | Linux Engine, Desktop VM, or remote daemon? | docker info, context endpoint |
| 7. Application | Is the app reading/writing the expected target? | Logs, in-container ls/stat |
| 8. Correction | What is the smallest change? | Narrow source, correct owner, scoped label, or config fix |
2. Intentionally broken example: preserve the missing-source error
This is the same failure category as the Lesson 2 exercise, now treated diagnostically. The path is intentionally absent and stays inside the lab directory.
mkdir -p da21-diag/evidence
rm -rf da21-diag/not-present
set +e
docker run --rm --mount type=bind,src="$(pwd)/da21-diag/not-present",dst=/data busybox:1.36.1 ls /data >da21-diag/evidence/stdout.txt 2>da21-diag/evidence/stderr.txt
status=$?
set -e
printf 'exit=%s
' "$status" | tee da21-diag/evidence/exit.txt
cat da21-diag/evidence/stderr.txt
docker context show | tee da21-diag/evidence/context.txt
docker context inspect "$(docker context show)" --format '{{json .Endpoints.docker.Host}}' | tee da21-diag/evidence/endpoint.txt
test ! -e da21-diag/not-present && echo 'source remains absent'
The least-destructive correction is to create the intended source explicitly if it truly belongs on this daemon host, or fix the context/path if it does not. Switching to short syntax merely to suppress the error would erase useful evidence.
3. Remote context: client-local path is not remote-daemon state
A frequent mistake is running Docker from a laptop against a remote
daemon and assuming ./config refers to the laptop. It
does not. The remote daemon resolves the bind source. The secure
alternatives are to place the required data on that host through an
authorized deployment process, bake declared immutable data into the
image, or use a storage/config mechanism designed for remote
delivery.
4. Root-owned developer files
A container process running as UID 0 can create files in a writable bind that appear host-owned by root on native Linux. Preserve numeric ownership first. A common local-development correction is to run the container process as the developer's numeric UID/GID when the image supports it, rather than recursively changing broad host permissions.
# Native Linux diagnostic example; skip numeric ownership conclusions on Desktop if its sharing layer remaps IDs.
id
ls -ln da21-lab/host-rw 2>/dev/null || true
docker run --rm --user "$(id -u):$(id -g)" --mount type=bind,src="$(pwd)/da21-lab/host-rw",dst=/work busybox:1.36.1 sh -c 'echo user-matched > /work/user-matched.txt' 2>da21-diag/evidence/user-write.err || true
ls -ln da21-lab/host-rw 2>/dev/null || true
If the application requires a specific in-image user, solve ownership as a deliberate image/runtime contract rather than falling back to world-writable permissions.
5. SELinux denial: keep the control, fix the label contract
On SELinux hosts, a bind may be blocked even when Unix permissions
look correct. Capture getenforce and relevant audit
evidence. Then choose the documented shared/private relabel option
for the exact application directory if appropriate. Disabling
SELinux converts a scoped labeling problem into a host-wide security
regression.
6. Broad host mounts and Docker control interfaces
Giving an untrusted container access to host root or the Docker daemon control interface can effectively grant host-level authority. The remedy is architectural: remove that mount and expose only the specific data or API the workload truly needs. This course never uses those mounts as troubleshooting shortcuts.
7. Propagation surprises
If a nested host mount is not visible in a container, first inspect
whether propagation is actually required and whether the host mount
tree supports it. Do not jump from rprivate to
rshared globally. On Docker Desktop, propagation does
not work, so a Linux-only recipe is the wrong layer entirely.
8. Docker Desktop: correctness versus file-sharing performance
If a large source bind is slow on Desktop, prove the bottleneck before changing application behavior. Docker advises sharing only needed directories and using named volumes for database/cache data. Optional synchronized file sharing may help very large repositories, but mandatory course workflows remain free and use ordinary file sharing.
9. Unsafe shortcut → evidence-based correction
| Unsafe shortcut | Why it hides risk | Preferred correction |
|---|---|---|
| Disable SELinux | Removes host-wide enforcement | Inspect denial and apply correct scoped labeling |
| Make directory world-writable | Erases least-privilege boundary | Match UID/GID or grant narrow intended permissions |
| Mount a broader host tree | Expands read/write authority | Mount the smallest required subtree |
Switch to -v after missing-source error |
May silently create the wrong path | Correct the intended source or context |
| Use broader propagation | Can expose nested mounts bidirectionally |
Keep rprivate unless a concrete propagation
requirement exists
|
10. Exact cleanup
docker rm -f da21-ro da21-rw da21-inspect da21-tmpfs da21-prop-inspect 2>/dev/null || true
find da21-diag -maxdepth 2 -type f -print 2>/dev/null || true
# Review evidence, then remove only ./da21-diag and ./da21-lab when intentionally finished.
Knowledge check
A bind works locally but fails through a remote context. What should you verify first?
The active context/daemon endpoint and whether the source exists on that daemon host.
Why is changing to world-writable permissions a poor ownership fix?
It expands write authority for unrelated users/processes and hides the real UID/GID contract.
What is the safer response to an SELinux denial?
Preserve the denial, inspect policy/labels, and apply a scoped correct label strategy rather than disabling SELinux.
Why is mounting a broader host path not a neutral troubleshooting step?
It exposes additional host data and write surface, potentially turning a local issue into a host-compromise boundary.
What should you conclude if propagation guidance works on Linux Engine but not Docker Desktop?
The behavior is platform-specific; propagation is unsupported on Docker Desktop, so the design must use another mechanism.
Official references and version notes
-
Docker Docs — Bind mounts
— daemon-host paths, read-only mode,
--mountversus-v, recursive mounts, propagation, and SELinux labeling. - Docker Docs — tmpfs mounts — memory-backed lifetime, size/mode options, and cgroup memory accounting.
- Docker CLI — docker container run — mount and read-only-root-filesystem semantics.
-
Compose Specification — service volumes
— bind options,
create_host_path, SELinux labels, tmpfs, and volume subpaths. -
Docker Docs — Volumes
—
volume-subpathas a mount-type-specific subpath feature and contrast with bind mounts. - Docker Desktop — File sharing — host-to-VM sharing boundaries and performance guidance.
- Docker Desktop — Synchronized file shares — optional paid synchronized host-file caching for large repositories.
-
Docker Engine 29 release notes
— current Engine baseline and
bind-create-srcaddition in 29.3.
Docker Engine 29.8.1 and Docker Desktop 4.91.0 are current; Compose 5.5.1, Buildx 0.37.1, and BuildKit 0.33.0 are the upstream baselines used for compatibility discussion. The labs record the learner's actual versions because host kernel, Docker Desktop VM/file-sharing mode, SELinux, and remote-context topology materially affect mount behavior.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.