Proxmox VEX · LXC ContainersConfiguration
Bind mounts and mount points: sharing host storage with a container
What you'll learn
- Distinguish volume, bind and device mount points and say which one vzdump protects
- Predict the ownership a bind-mounted directory will have inside an unprivileged container
- Fix ownership with a host chown or a per-mount-point idmap, and choose between them
- Recognise why a container with a bind mount migrates badly, and what shared=1 actually asserts
Prerequisites
Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12
A container with 8 GiB of rootfs and 40 TiB of media on the host is the normal shape of an LXC deployment. Getting the media into the container is one line of configuration. Getting it in correctly involves two things that are not visible in that line: who owns the files once they are inside, and whether they are in the backup.
Both are decided by choices you make when you write the mount point, and both fail quietly.
Three kinds of mount point, and the one difference that matters
mp0 through mp255 all use the same option syntax, but the value in the
volume position selects between three quite different things.
| Kind | Written as | Managed by PVE storage? | In vzdump? |
|---|---|---|---|
| Volume mount point | mp0: local-zfs:32,mp=/data | Yes - PVE allocates it | Yes, unless backup=0 |
| Bind mount point | mp0: /srv/ct-media,mp=/media | No - a host path | No |
| Device mount point | mp0: /dev/disk/by-id/…,mp=/data | No - a host block device | No |
The documentation is explicit that the contents of bind and device mount
points are not backed up with vzdump, and that the backup option applies
to volume mount points only. There is no flag that changes this.
Adding a bind mount
set -euo pipefail
CTID=200
HOSTDIR=/srv/ct-media
# The source must exist and must not contain symlinks anywhere in its path.
mkdir -p "$HOSTDIR"
pct set "$CTID" --mp0 "$HOSTDIR,mp=/media"
pct config "$CTID" | grep -E '^(rootfs|mp[0-9]+):'Options worth knowing, all documented on mp[n]:
| Option | Effect |
|---|---|
mp= | Path inside the container. Required. |
ro=1 | Mount read-only. The cheapest real safety control on this page. |
backup=1 | Volume mount points only. Silently irrelevant on a bind mount. |
shared=1 | Marks a non-volume mount point as “available on all nodes”. |
replicate=0 | Exclude a volume from storage replication jobs. Default is 1. |
mountoptions= | Extra mount options, semicolon-separated. |
idmap= | Per-mount UID/GID remapping. The subject of the next section. |
keepattrs=1 | Inherit UID, GID and mode from the mount point directory if it already exists. |
Why the files show up as nobody:nogroup
Here is the moment every operator meets once.
# stat -c '%U:%G %u:%g %n' /srv/ct-media
pct exec 200 -- stat -c '%U:%G %u:%g %n' /mediaroot:root 0:0 /srv/ct-media
nobody:nogroup 65534:65534 /mediaIllustrative output
Nothing is broken. This is the user namespace working exactly as designed.
An unprivileged container runs inside a user namespace whose mapping is, by
default, container UID 0..65535 onto host UID 100000..165535. That
mapping is declared for the root user in /etc/subuid and /etc/subgid:
# cat /etc/subuid /etc/subgidroot:100000:65536
root:100000:65536Illustrative output
So the arithmetic runs in both directions:
- A file that a container process creates as container UID
0is written to disk as host UID100000. - A file on disk owned by host UID
0has no container-side ID, because0is outside the mapped range100000..165535. The kernel reports it as the overflow UID,65534, which/etc/passwdrenders asnobody.
nobody:nogroup is not an error message. It is the kernel saying “this
ownership does not exist in your namespace.”
Two ways to fix ownership, and when to use each
Option 1: chown on the host to the mapped ID
The blunt, portable fix. Decide what the container-side owner should be, add
100000, and set that on the host.
set -euo pipefail
HOSTDIR=/srv/ct-media
BASE=100000 # first host UID of the container's default map
# container root (0) -> host 100000
chown -R "$BASE:$BASE" "$HOSTDIR"
# container uid 1000 (a service account inside) -> host 101000
# chown -R "$((BASE + 1000)):$((BASE + 1000))" "$HOSTDIR/app"
stat -c '%u:%g %n' "$HOSTDIR"This works, and it is what most estates do. Its limitation is that the data
now belongs to one container’s namespace. A host process, or a second
container with a different offset, sees 100000 and has no idea what it is.
Option 2: a per-mount-point idmap
PVE supports a custom mapping on the mount point itself, overriding the
container’s default for that mount only. The syntax is
type:container:disk:range-size, semicolon-separated, where type is u
for UIDs or g for GIDs.
set -euo pipefail
CTID=200
# On disk the tree is owned by 1000:1000 (a host service account).
# Inside the container it should appear as 1000:1000 as well.
pct set "$CTID" --mp0 '/srv/ct-media,mp=/media,idmap=u:1000:1000:1;g:1000:1000:1'
pct reboot "$CTID"
pct exec "$CTID" -- stat -c '%u:%g %n' /mediaThe documented special value passthrough identity-maps every ID on that
mount, so the container sees exactly the on-disk ownership.
| Situation | Use |
|---|---|
| Directory exists only to serve this container | chown to 100000-relative IDs |
| Host and container both read and write the tree | idmap= for the specific IDs involved |
| Two containers share the same tree | idmap= in both, mapping to the same on-disk IDs |
| Container is untrusted | ro=1, and neither of the above |
You are reaching for passthrough | Ask whether a volume mount point solves it instead |
Migration, and what shared=1 really claims
A volume mount point on shared storage migrates because PVE knows where the
data is and that the target node can reach it. A bind mount is a host path,
and PVE has no way to know whether /srv/ct-media on pve-02 is the same
data as /srv/ct-media on pve-01, a different NFS export, or an empty
directory.
shared=1 is documented as marking the mount point “as available on all
nodes”. Read that as an assertion you are making, which PVE then trusts.
Verification, and undo
A mount point that “looks right” in pct config proves only that the config
parsed. These four checks can each fail.
set -euo pipefail
CTID=200
INNER=/media
# 1. The container agrees it is a mount, not just a directory.
pct exec "$CTID" -- findmnt -no SOURCE,FSTYPE,OPTIONS "$INNER"
# 2. It is not empty when it should not be.
pct exec "$CTID" -- sh -c "ls -A '$INNER' | head -5"
# 3. The service account inside can actually write.
pct exec "$CTID" -- runuser -u www-data -- \
sh -c "touch '$INNER/.writetest' && rm '$INNER/.writetest'" \
&& echo 'write OK'
# 4. Ownership resolves to a name, not to nobody.
pct exec "$CTID" -- stat -c '%U:%G %n' "$INNER"Check 3 is the one that matters. ls succeeding tells you the mount exists;
only a write as the account the application actually runs as tells you the
mapping is right.
set -euo pipefail
CTID=200
pct set "$CTID" --delete mp0
pct reboot "$CTID"
pct config "$CTID" | grep -E '^mp[0-9]+:' || echo 'no mount points remain'Removing a volume mount point behaves differently: the volume becomes an unused disk on the container config rather than disappearing, and you delete it deliberately afterwards. That asymmetry is a feature - PVE will not silently destroy storage it allocated.
Knowledge check
Knowledge check · 5 questions
Q1. A container with a bind mount at /media is backed up nightly by vzdump to PBS. The container is destroyed and restored from last night’s snapshot. What is the state of /media?
Q2. You bind mount /srv/ct-media, owned root:root on the host, into an unprivileged container. Inside, ls shows the directory as nobody:nogroup. Why?
Q3. Which are sound reasons to prefer a per-mount-point idmap= over chowning the host directory? Select all that apply.
Q4. Setting shared=1 on a bind mount point causes Proxmox to verify that the path holds the same data on every node before allowing migration.
Q5. A directory should appear as owned by UID 1000 inside an unprivileged container using the default mapping. Which host UID should own it on disk?
Passing score: 75%. Answers are checked in this browser.