Skip to main content
RunBook Academy

Docker & ContainersVIII Β· StorageBind mounts

Bind mounts β€” host paths in containers

Intermediate⏱ ~24 min

What you'll learn

  • Use bind mounts safely and predict what they hide
  • Choose between `-v` and `--mount` and know why the failure modes differ
  • Recognise the security implications of bind mounts
  • Apply SELinux labels without relabelling the wrong directory

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-12

Not yet marked complete on this device.

A bind mount is a host directory mounted directly into the container. The container sees the host’s actual files; there is no Docker-managed storage layer.

Bind mounts are the most powerful and most dangerous storage primitive in Docker. Used well, they let the host and container share state. Used poorly, they are how an attacker breaks out of a container β€” and, more often, how a deployment silently starts serving nothing.

When to use bind mounts

  • Development. Mount your source tree into a container to iterate quickly. The container sees your edits without rebuilds.
  • Configuration. Mount a single config file to inject settings without rebuilding the image.
  • Host integration. Monitoring agents that must read real host state: /proc, /sys, a log directory.

When NOT to use bind mounts

  • Production data. Volumes are Docker-managed and portable; a bind mount ties the container to one host’s directory layout.
  • Multi-host deployments. Bind mounts are host-local. Schedule the container elsewhere and the path points at nothing β€” or worse, at something else.
  • Database data. Same reason, plus the ownership problems covered in the permissions lesson.

The failure that costs a night

The diagnosis is a one-liner, and it is worth putting in a runbook:

Read-only / Safewhat did the mount hide
CONTAINER=web
MOUNTPOINT=/etc/nginx/conf.d

# What the running container sees
docker exec "$CONTAINER" ls -la "$MOUNTPOINT"

# What the IMAGE has at that path, with no mounts in the way
IMAGE=$(docker inspect --format '{{.Config.Image}}' "$CONTAINER")
docker run --rm --entrypoint ls "$IMAGE" -la "$MOUNTPOINT"

If the first is empty and the second is not, the mount is the problem and the application is innocent.

Read-only / Safelist every mount
CONTAINER=web
docker inspect --format '{{range .Mounts}}{{.Type}}  {{.Source}} -> {{.Destination}} (rw={{.RW}}){{"\n"}}{{end}}' "$CONTAINER"
Read-only / Safemounts output
$ docker inspect --format '{{range .Mounts}}...{{end}}' web
bind  /srv/app/config -> /etc/nginx/conf.d (rw=true)
volume  /var/lib/docker/volumes/web-cache/_data -> /var/cache/nginx (rw=true)

Illustrative output

-v versus --mount

The two forms are not equivalent, and the difference is exactly the behaviour that hides the bug above.

-v / --volume--mount
Host path missingCreated as an empty directoryError, container does not start
SyntaxColon-separated positionalComma-separated key=value
Ambiguity-v foo:/data is a named volume; -v ./foo:/data is a bindtype=bind or type=volume is explicit
Creating the source deliberatelyAlwaysOnly with bind-create-src
# Fails loudly if /srv/app/config does not exist β€” which is what you want
docker run -d --name web \
  --mount type=bind,src=/srv/app/config,dst=/etc/nginx/conf.d,readonly \
  nginx:1.27

Use --mount for anything that runs unattended. The extra typing buys you a container that refuses to start rather than one that starts wrong.

SELinux labels

On SELinux-enforcing hosts (RHEL, CentOS Stream, Fedora, Rocky), a bind-mounted host path carries whatever label it already has, and the container’s container_t domain is not permitted to write it. The symptom is Permission denied on a path whose Unix permissions are obviously fine.

The :z and :Z suffixes tell Docker to relabel the source:

  • :z β€” label the content as shared between containers. Multiple containers can use the path.
  • :Z β€” label the content as private and unshared to this container. More restrictive, and it means no other container (and no host service) can use it.
docker run -v /srv/appdata:/data:z nginx:1.27

Note also that with Swarm services, :z, :Z and :ro on bind mounts are ignored β€” a genuine difference in behaviour between docker run and docker service create.

Security implications

Bind mounts bypass the container’s filesystem isolation. A container with / bind-mounted from the host can read any file its UID can read, modify any file its UID can write, and execute host binaries.

Without user-namespace remapping, a container running as root is UID 0 on the host. The container boundary for a bind-mounted path is the Unix permission check and nothing else.

The Docker socket deserves its own mention. -v /var/run/docker.sock:/var/run/docker.sock is a bind mount, and it gives the container the ability to start another container with any mount and any privilege it likes. It is host root with extra steps. It has a dedicated lesson; treat it as a bind mount whose source happens to be an API.

Verifying a bind mount actually worked

β€œThe container is running” is not verification. These two commands distinguish working from broken:

Read-only / Safeverify
CONTAINER=web
MOUNTPOINT=/etc/nginx/conf.d

# 1. The mount point is non-empty and holds what you expect
docker exec "$CONTAINER" find "$MOUNTPOINT" -maxdepth 1 -type f -printf '%p %s\n'

# 2. It is read-only if you asked for read-only.
#    Expect "Read-only file system" here, and treat success as the failure.
docker exec "$CONTAINER" touch "$MOUNTPOINT/.writetest" && echo 'WRITABLE - check your readonly flag' || echo 'read-only as intended'

The second check is the one that can fail usefully. A readonly flag that was silently dropped β€” because someone converted a --mount line back to -v and lost the :ro β€” leaves you with a container that can overwrite the config the host is managing.

Knowledge check

Knowledge check Β· 5 questions

  1. Q1. You bind-mount an EMPTY host directory over `/etc/nginx/conf.d`, which the image populates. What does nginx see?

  2. Q2. You mistype the host path in `docker run -v /srv/aplication:/etc/app IMAGE`. What happens?

  3. Q3. Which statements about the `:Z` SELinux option are true? Select all that apply.

  4. Q4. By default, a filesystem mounted on the host underneath a bind-mount source AFTER the container started becomes visible inside the container.

  5. Q5. Which check distinguishes a working read-only bind mount from one that lost its flag?

Passing score: 75%. Answers are checked in this browser.