What it is and how it differs from a symlink
A bind mount makes the same set of inodes reachable through two paths. It does not duplicate data and it is not a link at the filesystem level. It is an operation in the kernel's VFS layer.
sudo mount --bind /var/log /home/serge/logs
ls /var/log/syslog
ls /home/serge/logs/syslog # same file, same inode
How it differs from a symbolic-link:
| Property | symlink | bind mount |
|---|---|---|
| What it is | inode pointer in the FS | VFS mount |
| After reboot | survives | gone (needs fstab) |
chroot sees | broken if the target is outside | works, the target is already "inside" |
realpath | resolves | does NOT resolve (shows as the target path) |
| Needs root | no | YES (mount(2)) |
| Can be read-only | no | yes (-o ro) |
The main strength of a bind mount: inside a chroot or namespaces, only the real path is visible. A symlink to a path outside would be broken.
Basic use
# 1. Directory onto a directory
sudo mount --bind /var/log /mnt/logs
# 2. File over a file (a powerful trick)
sudo mount --bind /tmp/my-fake-resolv.conf /etc/resolv.conf
▸now /etc/resolv.conf points to our file, the original is still there
# 3. Unmount
sudo umount /mnt/logs
Overriding a file by binding another file on top of it is a common feature in containers and hot fixes: do not edit the original, lay a bind on top of it on the fly.
Read-only bind: a two-step move
The -o ro option on the initial mount is ignored by the kernel during the
first bind. You need a separate remount step:
sudo mount --bind /opt/data /srv/ro-data
sudo mount -o remount,ro,bind /srv/ro-data # now it is really RO
Or in one command with --mkdir plus -o:
sudo mount --bind -o ro /opt/data /srv/ro-data # works directly on util-linux >= 2.27
Check it:
findmnt /srv/ro-data
# TARGET SOURCE FSTYPE OPTIONS
# /srv/ro-data /dev/sda1[/opt/data] ext4 ro,relatime
The SOURCE column with [/...] in brackets is the sign of a bind mount.
--bind vs --rbind
sudo mount --bind / /mnt/host # bind: does NOT carry submounts
sudo mount --rbind / /mnt/host # rbind: recursive, with /proc, /sys, /dev, and so on
--rbind matters when the source directory contains nested mount points (the
classic example is bind-mounting the whole root filesystem for a chroot).
Without the r, you get empty /proc, /sys, and /dev inside.
Mount propagation: what it is about
When a new mount is made inside a bind mount, should it be visible outside? That is controlled by the propagation flag:
| Flag | Behavior |
|---|---|
shared | new mounts are visible both ways (default on systemd systems) |
slave | mounts from the master are visible to us, but not the reverse |
private | mounts are invisible both ways |
unbindable | cannot be bound at all |
Setting it:
sudo mount --make-private /srv/jail
sudo mount --make-rslave / # recursive, for the whole tree
Why know this: Docker and systemd lean on propagation heavily. If something with
bind in your /etc/fstab behaves oddly across a reboot, check
findmnt -o TARGET,PROPAGATION.
Example: mount --bind / for a chroot inherits the same propagation as /. If
you then unmount something inside the chroot, it also unmounts outside (when
propagation is shared). That is not obvious. The fix is --make-rprivate on the
bind copy.
fstab: a persistent bind
# /etc/fstab
/var/log /home/serge/logs none bind 0 0
/opt/data /srv/ro-data none bind,ro 0 0
/home /chroot/home none rbind 0 0
/tmp/conf /etc/myapp.conf none bind 0 0
The FS type is none (it does not apply to a bind). The options go through
bind or rbind. To make systemd-fstab-generator pick it up without a reboot:
sudo systemctl daemon-reload
sudo mount -a
In practice
chroot and rescue
for fs in proc sys dev dev/pts run; do
sudo mount --bind /$fs /mnt/$fs
done
sudo chroot /mnt
Without binds for /proc and /dev, many commands inside a chroot will fail.
Moving data without changing the path
Say /var/lib/postgres is filling up the root filesystem and you need to move it
to a separate disk without editing the Postgres config:
sudo systemctl stop postgresql
sudo rsync -aHAX /var/lib/postgres/ /data/postgres/
sudo mv /var/lib/postgres /var/lib/postgres.old
sudo mkdir /var/lib/postgres
echo '/data/postgres /var/lib/postgres none bind 0 0' | sudo tee -a /etc/fstab
sudo mount -a
sudo systemctl start postgresql
Postgres keeps reading /var/lib/postgres, but the data physically lives on /data.
Read-only layer in a container
sudo mount --bind /etc /run/jail/etc
sudo mount -o remount,ro,bind /run/jail/etc
systemd PrivateTmp
When a service has PrivateTmp=yes, systemd does a bind plus namespace under the
hood: it creates an empty directory and bind-mounts it over /tmp for that
service. The service does not see the host's /tmp.
Limits
- You cannot bind a file onto a directory or the reverse. Only file to file, directory to directory.
- The target must exist. For a file, create it empty; for a directory,
mkdir. umountremoves only the bind, the original stays:bashsudo umount /mnt/logs # /var/log is still there
ducounts twice if both paths fall into the walk:bashdu -sh /var/log /mnt/logs # totals up to 2× the size
Debugging
findmnt --types=none # all bind/rbind mounts
findmnt -o TARGET,SOURCE,OPTIONS,PROPAGATION /srv/jail
cat /proc/self/mountinfo # raw dump of all mounts
mount | grep bind # the old-school way
Bind mounts in mountinfo have the form <src-fs>[/sub/path] in SOURCE. That is
exactly how findmnt knows it is a bind.