Chapter 21Lesson 02~150 minutes

Bind Mounts, Read-Only Mounts, tmpfs, Mount Propagation, Subpaths, SELinux Labels, and Host Coupling: Guided Hands-On Workflow and Core Operations

Compare read-only and writable bind mounts, strict versus auto-created sources, tmpfs, mount inspection, subpath behavior, and platform-specific security constraints.

Hands-on--mount-vtmpfsInspection

Learning objectives

  • Create a disposable host directory and prove the difference between read-only and writable bind authority.
  • Compare strict --mount source handling with -v auto-creation behavior and verify the result.
  • Create and inspect a bounded tmpfs mount and prove its data disappears with the container.
  • Demonstrate mount-type-specific subpath behavior without implying undocumented bind-subpath support.
  • Explain SELinux and propagation constraints without weakening host security.
Lab safety. Use only ./da21-lab beneath the current working directory, BusyBox, and exact container/volume names beginning da21-. Do not mount host root, Docker control sockets, credential directories, or production paths. No daemon setting, security policy, or firewall setting is changed.

1. Preflight and workspace

docker version
docker info
docker context show
docker compose version || true
docker buildx version || true

docker ps -a --no-trunc
docker volume ls
docker pull busybox:1.36.1
mkdir -p da21-lab/host-ro da21-lab/host-rw da21-lab/evidence
printf 'academy-readonly
' > da21-lab/host-ro/source.txt
printf 'before
' > da21-lab/host-rw/state.txt

pwd
ls -ln da21-lab/host-ro da21-lab/host-rw

docker context inspect "$(docker context show)" --format '{{json .Endpoints.docker.Host}}' | tee da21-lab/evidence/context-endpoint.txt

The source directories are synthetic and intentionally narrow. If the active context points to a remote daemon, stop: the local paths shown by pwd are client paths and may not exist on that daemon. The mandatory writable-bind steps assume a local Engine or Docker Desktop context.

2. Read-only bind: prove visibility without mutation

The container may read the file but must not modify it. --mount makes the source/target/type explicit.

docker run --rm   --name da21-ro   --label devops-academy.lab=ch21   --mount type=bind,src="$(pwd)/da21-lab/host-ro",dst=/input,readonly   busybox:1.36.1 sh -c '
    set -eu
    cat /input/source.txt
    if echo blocked > /input/should-not-write.txt 2>/tmp/err; then
      echo "unexpected write success" >&2; exit 1
    else
      echo "write blocked as expected"
      cat /tmp/err
    fi
  ' | tee da21-lab/evidence/readonly.txt

ls -la da21-lab/host-ro

No new host file should exist. A read-only bind constrains writes at the mount boundary rather than relying only on application convention.

3. Writable bind: prove host-visible side effects

Now grant write authority only to the disposable host-rw directory.

docker run --rm   --name da21-rw   --label devops-academy.lab=ch21   --mount type=bind,src="$(pwd)/da21-lab/host-rw",dst=/work   busybox:1.36.1 sh -c '
    set -eu
    printf "changed-by-container
" >> /work/state.txt
    printf "created-by-container
" > /work/container-created.txt
    ls -ln /work
  ' | tee da21-lab/evidence/writable-container.txt

cat da21-lab/host-rw/state.txt
cat da21-lab/host-rw/container-created.txt
ls -ln da21-lab/host-rw | tee da21-lab/evidence/writable-host.txt

The container has modified client-visible host files because this lab uses a local/Desktop context. Numeric ownership may differ by platform and Desktop file-sharing implementation, so record rather than assume it.

4. Inspect mount state before the container exits

For stronger evidence, create a sleeping container, inspect its mount object, then remove the exact container.

docker create \
  --name da21-inspect \
  --label devops-academy.lab=ch21 \
  --mount type=bind,src="$(pwd)/da21-lab/host-rw",dst=/work,readonly   busybox:1.36.1 sleep 300

docker inspect da21-inspect --format '{{json .Mounts}}'   | tee da21-lab/evidence/mount-inspect.json

docker rm da21-inspect

Look for type, source, destination, RW:false, and propagation. The source recorded by the daemon is the authoritative host-side path it resolved.

5. Strict source handling versus silent creation

This intentionally broken example preserves the first error. The first command should fail because --mount requires the source path to exist by default.

rm -rf da21-lab/missing-strict da21-lab/missing-short

if docker run --rm   --mount type=bind,src="$(pwd)/da21-lab/missing-strict",dst=/data   busybox:1.36.1 true   >da21-lab/evidence/strict.stdout 2>da21-lab/evidence/strict.stderr; then
  echo "unexpected success"
else
  echo "strict mount failed as expected"
  cat da21-lab/evidence/strict.stderr
fi

test ! -e da21-lab/missing-strict && echo 'strict source was not created'

# Demonstrate the historical -v behavior only inside the disposable lab directory.
docker run --rm   -v "$(pwd)/da21-lab/missing-short:/data"   busybox:1.36.1 sh -c 'test -d /data && echo mounted'

test -d da21-lab/missing-short && echo 'short syntax created the missing directory'

The lesson is not “always avoid -v.” The lesson is to know whether source creation is intended. Current Engine also offers explicit bind-create-src with --mount; use that only when your installed Engine supports it and creation is part of the design.

6. tmpfs: bounded ephemeral state

Create a container with a 1 MiB tmpfs and a 64 MiB container memory limit. The tmpfs has no host source path.

docker run -d \
  --name da21-tmpfs \
  --label devops-academy.lab=ch21 \
  --memory 64m \
  --mount type=tmpfs,dst=/run/lab,tmpfs-size=1048576,tmpfs-mode=1770   busybox:1.36.1 sh -c 'printf "ephemeral
" > /run/lab/token.txt; sleep 300'

docker inspect da21-tmpfs --format '{{json .Mounts}}'   | tee da21-lab/evidence/tmpfs-inspect.json

docker exec da21-tmpfs sh -c 'cat /run/lab/token.txt; df -h /run/lab; ls -ldn /run/lab'

docker rm -f da21-tmpfs

docker run \
  --rm \
  --mount type=tmpfs,dst=/run/lab,tmpfs-size=1048576,tmpfs-mode=1770   busybox:1.36.1 sh -c 'test ! -e /run/lab/token.txt && echo "previous tmpfs data is gone"'

7. Subpath is mount-type-specific

Current Docker documentation explicitly supports volume-subpath for named volumes. This is useful to teach the concept without inventing a bind-mount option: for a bind mount, the normal way to expose only a subtree is to bind the narrower host path itself.

docker volume create --label devops-academy.lab=ch21 da21-subpath

docker run \
  --rm \
  --mount type=volume,src=da21-subpath,dst=/seed   busybox:1.36.1 sh -c 'mkdir -p /seed/team-a /seed/team-b; echo A > /seed/team-a/value; echo B > /seed/team-b/value'

docker run \
  --rm \
  --mount type=volume,src=da21-subpath,dst=/data,volume-subpath=team-a,readonly   busybox:1.36.1 sh -c 'cat /data/value; test ! -e /data/../team-b/value || true'

docker volume rm da21-subpath

If the installed Engine predates volume-subpath support, record that as a compatibility limitation and skip this optional comparison. The mandatory bind-mount lessons do not depend on it.

8. SELinux and propagation: inspect and reason, do not weaken the host

On an SELinux host, check getenforce if available. If access is denied, inspect labels and use the documented z/Z semantics only for a deliberately scoped path; do not disable SELinux. Bind propagation defaults to rprivate, is Linux-host-specific, requires the host mount setup to support propagation, and does not work on Docker Desktop. For this cross-platform lab, propagation remains a conceptual/inspection exercise rather than a host-mount mutation.

command -v getenforce >/dev/null && getenforce || true

docker run -d --name da21-prop-inspect   --mount type=bind,src="$(pwd)/da21-lab/host-ro",dst=/data,readonly   busybox:1.36.1 sleep 300

docker inspect da21-prop-inspect --format '{{range .Mounts}}{{.Type}} {{.Destination}} RW={{.RW}} Propagation={{.Propagation}}{{println}}{{end}}'
docker rm -f da21-prop-inspect

9. Challenge: choose the correct layer

Your laptop file exists, but a container launched through a remote Docker context reports “bind source path does not exist.” Which layer is wrong? The container image is not the first suspect. Prove the active context and daemon endpoint, then ask whether the source exists on that daemon host.

10. Exact cleanup

docker rm -f da21-ro da21-rw da21-inspect da21-tmpfs da21-prop-inspect 2>/dev/null || true
docker volume rm da21-subpath 2>/dev/null || true

# Review evidence before deleting the disposable host paths.
find da21-lab -maxdepth 2 -type f -print
# Delete only ./da21-lab when you intentionally finish the lab.
Next lesson

Next: Bind Mounts, Read-Only Mounts, tmpfs, Mount Propagation, Subpaths, SELinux Labels, and Host Coupling: Configuration, Design Choices, and Tradeoffs

Compare bind, volume, and tmpfs choices; development versus production coupling; propagation modes; SELinux labeling; and Compose defaults.

Knowledge check

Why did the strict --mount example fail?

What risk does -v demonstrate when the source path is misspelled?

Why is tmpfs suitable for disposable runtime material?

Why is the subpath example a named-volume example rather than a bind-mount claim?

What should you do instead of disabling SELinux after a mount denial?

Official references and version notes

Version baseline, verified 2026-09-21.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.