This short article explains a simple, practical one-shot workflow that lets a non-root user create a root filesystem (rootfs) with debootstrap and install packages inside it using apt, by running the operations inside an unprivileged user namespace where the user appears as root. It includes a ready-to-run script, key explanations, and troubleshooting hints.
Run the provided script as a regular user. It uses unshare --user --map-root-user to create a user/mount/pid namespace where your UID is mapped to UID 0 inside that namespace. Inside that namespace the script mounts necessary pseudo-filesystems, runs debootstrap to populate a target directory with a minimal Debian rootfs, then chroots into that rootfs and runs apt-get to install packages (e.g. vim). When finished, it unmounts the bind mounts and leaves the populated rootfs at the path you chose.
- Kernel must allow unprivileged user namespaces. Check with:
sysctl kernel.unprivileged_userns_cloneValue 1 → OK. If it’s 0, ask the system administrator (or enable it if you control the host).
- Host must have
unshare(util-linux) anddebootstrapinstalled. Some systems may requirenewuidmap/newgidmapand proper/etc/subuid/etc/subgidentries for UID/GID mapping to work correctly.
Replace ROOT=/path/to/root with the desired target directory.
#!/bin/bash
set -euo pipefail
ROOT=/path/to/root # ← change this
DEBIAN_SUITE=stable # e.g. stable, bookworm, focal
MIRROR="http://deb.debian.org/debian"
ARCH=$(dpkg --print-architecture)
# 1) create directories
mkdir -p "$ROOT"
for d in dev proc sys run tmp etc/apt/; do
mkdir -p "$ROOT/$d"
done
# 2) copy resolv.conf so apt can resolve names (or bind mount if preferred)
cp -L /etc/resolv.conf "$ROOT/etc/resolv.conf" || true
# 3) run inside a new user/mount/pid namespace where we map our user to root
unshare --user --map-root-user --mount --pid --fork /bin/bash - <<'INNER'
set -euo pipefail
ROOT=''"$ROOT"''
ARCH=''"$ARCH"''
DEBIAN_SUITE=''"$DEBIAN_SUITE"''
MIRROR=''"$MIRROR"''
# mount pseudo-filesystems into the target (namespace-local mounts)
mount -t proc proc "$ROOT/proc"
mount --rbind /dev "$ROOT/dev"
mount --rbind /sys "$ROOT/sys" || true
mount --rbind /run "$ROOT/run" || true
mkdir -p "$ROOT/var/cache/apt/archives"
mount --bind /tmp "$ROOT/tmp" || true
# 4) ensure debootstrap exists and run it
if ! command -v debootstrap >/dev/null 2>&1; then
echo "debootstrap not found on host; install it first." >&2
exit 1
fi
debootstrap --variant=minbase --arch="$ARCH" "$DEBIAN_SUITE" "$ROOT" "$MIRROR"
# 5) chroot and install packages (example: vim)
chroot "$ROOT" /bin/bash -c "apt-get update && apt-get -y install --no-install-recommends vim"
# 6) cleanup (unshare-internal unmounts)
umount -l "$ROOT/proc" || true
umount -l "$ROOT/sys" || true
umount -l "$ROOT/dev" || true
umount -l "$ROOT/run" || true
umount -l "$ROOT/tmp" || true
INNER
echo "done. rootfs is in $ROOT"-
Why
--user --map-root-userworks--usercreates a user namespace;--map-root-usermaps your (host) non-privileged UID to 0 inside that namespace. Inside that namespace you effectively act as root for namespace-local operations (mount, chroot, etc.), while remaining unprivileged on the host. -
debootstrapcan run from the hostdebootstrapunpacks a minimal Debian filesystem into a target directory. If you run it after you set up the mounts inside the namespace, it runs fine —debootstrapitself expects root, and the namespace-root satisfies that. -
Why
chrootis recommended foraptWhile APT offers-o Dir::Root=...style options, the reliable and simple method is tochrootinto the freshly created rootfs and runapt-get update && apt-get install .... Make sure/proc,/dev,/sys, and DNS are available inside the chroot. -
Networking and name resolution Put a working
/etc/resolv.confinto the target (copy or bind-mount) soaptcan resolve package mirrors. -
Cleanup Unmount the bind mounts when finished. Although the user namespace is destroyed after
unshareexits, mounts may persist against the target directory until explicitly unmounted — useumount -lwhere appropriate.
unshare: unshare failed: Operation not permitted→ kernel unprivileged user namespaces likely disabled. Checksysctl kernel.unprivileged_userns_cloneand consult the admin.- UID/GID mapping errors or failure to create the namespace → the system may require
newuidmap/newgidmapand entries in/etc/subuidand/etc/subgid. Install theuidmaptools and configure subuid/subgid per your distribution’s docs. debootstrapnot found → install it on the host (Debian/Ubuntu:sudo apt install debootstrap, but you need admin for that).aptfailing inside chroot → verify/proc,/dev,/sys,/runand/etc/resolv.confare present in the chroot and that the mirror URL and suite are correct.
APT supports -o overrides (e.g. Dir::Root and other Dir::* options), but making APT behave correctly entirely from the host often requires multiple Dir:: overrides and is more fiddly. For clarity and compatibility, chroot inside a mapped user namespace is simpler and more robust for general use.