Rootless Docker: installatie, beperkingen en permissieproblemen oplossen
Rootless Docker is Docker draaien zonder dat de Docker-daemon (en je containers) rootrechten nodig hebben. Dat klinkt als “gewoon een vinkje”, maar in de praktijk verandert het best wat: netwerk, poorten, storage, cgroups en vooral permissies gedragen zich anders dan bij de klassieke rootful installatie.
In deze tutorial leer je:
- wat Rootless Docker precies is en hoe het werkt
- hoe je het installeert (Linux, met focus op Debian/Ubuntu/Fedora/Arch-achtige systemen)
- hoe je het correct configureert (PATH, systemd user services, contexten)
- welke beperkingen je tegenkomt (netwerk, poorten <1024, overlayfs, cgroups)
- hoe je de meest voorkomende permissieproblemen oplost (volumes, bind mounts, sockets, UID/GID mapping)
- hoe je Rootless Docker in CI en development werkbaar maakt
Doelgroep: Linux-gebruikers die Docker willen draaien zonder
sudoen zonder een root-daemon, maar wel met realistische verwachtingen en praktische oplossingen.
1. Wat is Rootless Docker?
1.1 Rootful vs rootless in één zin
- Rootful Docker:
dockerddraait als root, containers worden door root beheerd; gebruikers praten via een root-owned socket (/var/run/docker.sock). - Rootless Docker:
dockerddraait als jouw user; containers draaien in een user namespace met UID/GID-mapping; de socket zit in jouw home ($XDG_RUNTIME_DIR/docker.sock).
1.2 Waarom rootless?
- Beperkt impact van een container escape: als een container uitbreekt, breekt hij uit naar jouw user, niet naar root.
- Geen
sudonodig: je kunt Docker gebruiken op systemen waar je geen admin bent (bijv. shared servers, sommige enterprise omgevingen). - Minder “docker group = root” risico: in rootful setups is lidmaatschap van de
docker-groep praktisch gelijk aan root-toegang.
1.3 Hoe maakt Docker dit mogelijk?
Rootless Docker leunt op:
- User namespaces: jouw user krijgt een range van sub-UIDs en sub-GIDs via
/etc/subuiden/etc/subgid. - RootlessKit: start en beheert de rootless omgeving (namespaces, port forwarding).
- slirp4netns (vaak): user-mode networking voor containers.
- fuse-overlayfs (vaak): overlay filesystem in user space als kernel overlayfs niet kan in rootless.
2. Vereisten en checks vooraf
2.1 Kernel en distro
Rootless werkt op moderne Linux-kernels (meestal 5.x+ is prima). Je hebt nodig:
newuidmapennewgidmap(uit pakketuidmapop Debian/Ubuntu)- subuid/subgid entries voor jouw user
iptables/nftis minder relevant (rootless gebruikt vaak user-mode networking), maar kan nog meespelen afhankelijk van setup- optioneel:
systemd --uservoor autostart
2.2 Controleer subuid/subgid
Rootless Docker heeft subuid/subgid nodig zodat container-“root” kan mappen naar niet-root IDs op de host.
Check:
id
whoami
cat /etc/subuid | grep "^$(whoami):" || true
cat /etc/subgid | grep "^$(whoami):" || true
Je wil iets zien zoals:
jouwuser:100000:65536
Als dat ontbreekt (admin nodig):
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 jouwuser
Of handmatig (als beleid dat toestaat):
echo "jouwuser:100000:65536" | sudo tee -a /etc/subuid
echo "jouwuser:100000:65536" | sudo tee -a /etc/subgid
Installeer uidmap tools:
- Debian/Ubuntu:
sudo apt-get update
sudo apt-get install -y uidmap
- Fedora:
sudo dnf install -y shadow-utils
- Arch:
sudo pacman -S --needed shadow
Controleer of newuidmap bestaat:
command -v newuidmap
command -v newgidmap
3. Installatie van Rootless Docker
Er zijn meerdere routes. De meest universele is: installeer Docker Engine normaal, maar gebruik de rootless setup tool.
3.1 Docker Engine installeren (pakketmanager)
Debian/Ubuntu (voorbeeld met Docker repo):
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Fedora:
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Je hoeft de rootful daemon niet te starten voor rootless. Het pakket levert de binaries en de rootless scripts.
3.2 Rootless setup tool draaien
Docker levert dockerd-rootless-setuptool.sh. Draai:
dockerd-rootless-setuptool.sh install
Als alles goed gaat, zie je output met:
- waar de socket komt te staan (vaak:
unix:///run/user/1000/docker.sock) - instructies om
DOCKER_HOSTte zetten - instructies om de user systemd service te starten
Als je een fout krijgt over subuid/subgid of newuidmap, ga terug naar sectie 2.
3.3 PATH en environment variabelen
Vaak krijg je advies om dit toe te voegen aan ~/.bashrc of ~/.profile:
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
Controleer je runtime dir:
echo "$XDG_RUNTIME_DIR"
ls -la "$XDG_RUNTIME_DIR"
Meestal is dat /run/user/1000.
3.4 Starten via systemd (aanbevolen)
Start de user service:
systemctl --user start docker
systemctl --user status docker
Zet autostart aan:
systemctl --user enable docker
Zorg dat user services mogen draaien zonder actieve login (handig op servers):
sudo loginctl enable-linger "$(whoami)"
3.5 Test: werkt rootless?
docker version
docker info
docker run --rm hello-world
In docker info zie je vaak regels zoals:
rootless: trueSecurity Options: ... rootless
4. Docker contexten: rootful en rootless naast elkaar
Als je óók een rootful Docker hebt (bijv. op je laptop), is het handig om contexten te gebruiken.
Bekijk contexten:
docker context ls
Maak een rootless context:
docker context create rootless --docker "host=unix:///run/user/$(id -u)/docker.sock"
docker context use rootless
Terug naar default (rootful):
docker context use default
5. Netwerk in Rootless Docker (en waarom het anders is)
5.1 Waarom geen “normale” bridgen?
Rootful Docker maakt een Linux bridge (docker0), zet iptables/NAT regels, en kan direct poorten binden op het host-netwerk. Dat vereist root.
Rootless Docker kan dat niet zomaar, dus gebruikt het meestal:
- slirp4netns: user-mode NAT; containers krijgen een virtueel netwerk
- RootlessKit port forwarding: om host-poorten door te sturen naar container-poorten
Gevolgen:
- performance kan lager zijn dan rootful bridging
- sommige netwerkfeatures werken anders of niet (bijv. bepaalde multicast/low-level net features)
- ping/ICMP kan beperkt zijn in sommige setups
5.2 Poorten publiceren: -p werkt, maar met nuances
Voorbeeld:
docker run --rm -p 8080:80 nginx:alpine
curl -I http://127.0.0.1:8080
Dit werkt meestal prima.
Poorten < 1024 (privileged ports)
Als je -p 80:80 wilt, loop je tegen het feit aan dat lage poorten privileged zijn. Rootless kan niet zomaar op 80 luisteren.
Oplossingen:
- Gebruik een hogere poort (simpelst):
docker run --rm -p 8080:80 nginx:alpine
-
Gebruik authbind (niet overal beschikbaar) of setcap op rootlesskit (geavanceerd, distro-afhankelijk, vaak niet wenselijk).
-
Gebruik een reverse proxy die wél als root kan binden (bijv. system nginx/caddy) en proxy naar 8080.
Voorbeeld Nginx op host (rootful) naar rootless container:
- container:
docker run -d --name web -p 8080:80 nginx:alpine
- host nginx vhost:
server {
listen 80;
server_name example.test;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
5.3 Host network mode
--network host is in rootless vaak beperkt of niet hetzelfde als rootful. Verwacht dat “host mode” niet altijd de performance/gedragingen geeft die je kent.
Check:
docker run --rm --network host alpine ip addr
Als dit niet doet wat je verwacht: ontwerp je setup met gepubliceerde poorten of een user-defined bridge (rootless variant).
6. Storage drivers: overlay2 vs fuse-overlayfs
6.1 Waarom overlay2 soms niet kan
Rootful Docker gebruikt vaak overlay2 direct in de kernel. Rootless kan tegen beperkingen aanlopen (permissions, user namespaces).
Rootless valt vaak terug op fuse-overlayfs.
Check je storage driver:
docker info | grep -i "storage driver"
Als je fuse-overlayfs ziet: prima, maar iets minder performance.
Installeer fuse-overlayfs indien nodig:
- Debian/Ubuntu:
sudo apt-get install -y fuse-overlayfs
- Fedora:
sudo dnf install -y fuse-overlayfs
7. cgroups en resource limits (CPU/memory)
In rootful Docker kun je via cgroups netjes limiteren. In rootless is dit afhankelijk van je distro en cgroups v2.
Check:
docker info | grep -i cgroup -n
mount | grep cgroup
Als je cgroups v2 hebt en systemd user slices correct werken, kun je soms wél limits gebruiken, maar verwacht inconsistenties.
Test:
docker run --rm -m 256m --cpus 1 alpine sh -c "cat /sys/fs/cgroup/memory.max 2>/dev/null || true; sleep 1"
Als -m genegeerd wordt of errors geeft: dat is een bekende rootless beperking of configuratie-issue.
8. Permissieproblemen: de kern van rootless
Permissieproblemen komen vooral door UID/GID mapping. In de container ben je vaak root (UID 0), maar dat wordt op de host gemapt naar een niet-root subuid (bijv. 100000). Daardoor kan een bind mount naar je home ineens “geen rechten” geven, of bestanden worden aangemaakt met onverwachte ownership.
8.1 Begrijp UID mapping met een voorbeeld
Stel:
- jij bent host user
alicemet UID 1000 - subuid range:
alice:100000:65536
Binnen de container is root UID 0, maar op de host wordt dat vaak UID 100000.
Dus als een container een bestand schrijft als root naar een bind mount, kan dat op de host verschijnen als eigenaar 100000 (een “onbekende” UID).
8.2 Typische foutmeldingen
permission deniedbij schrijven naar een bind mount- bestanden in je projectmap krijgen “rare” owners (100000+)
chownin container werkt niet zoals verwacht- problemen met sockets (bijv. Docker socket, SSH agent)
9. Oplossingen voor permissieproblemen (praktisch)
9.1 Gebruik named volumes i.p.v. bind mounts (vaak het makkelijkst)
Named volumes worden beheerd door Docker in zijn eigen rootless storage, met consistente mapping.
Voorbeeld:
docker volume create appdata
docker run -d --name db -v appdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:16
Dit voorkomt dat je host filesystem ownership “vervuild” raakt.
9.2 Als je wél bind mounts nodig hebt: draai container met jouw UID/GID
Voor development is dit vaak de beste aanpak: laat processen in de container draaien als jouw user.
Voorbeeld:
docker run --rm \
-u "$(id -u):$(id -g)" \
-v "$PWD:/work" -w /work \
alpine sh -c "echo test > file.txt && ls -ln file.txt"
In plaats van root (0) schrijft hij als jouw UID.
Bij images die een eigen user verwachten (bijv. node), kun je ook een user aanmaken of bestaande gebruiken, maar -u is snel.
9.3 Compose: user instellen
compose.yaml:
services:
app:
image: node:22-alpine
user: "${UID}:${GID}"
working_dir: /app
volumes:
- ./:/app
command: ["sh", "-c", "npm ci && npm test"]
Start met:
export UID GID
UID=$(id -u) GID=$(id -g) docker compose up --build
9.4 chown op host: waarom het soms niet helpt
Als de container root (0) gemapt wordt naar 100000, dan kan chown root:root op host niet overeenkomen met “container root”. Andersom kan chown in de container niet altijd de host ownership aanpassen zoals je verwacht.
Als je bind mounts gebruikt, is “run as your UID” meestal betrouwbaarder dan proberen ownership te fixen.
9.5 Gebruik --userns=keep-id (waar beschikbaar/geschikt)
Sommige container runtimes ondersteunen keep-id concepten; Docker heeft in rootless contexten opties die hierop lijken via userns remapping. In de praktijk is -u $(id -u):$(id -g) het meest voorspelbaar. Als jouw Docker-versie --userns=keep-id ondersteunt in jouw workflow, kan dat een nettere oplossing zijn, maar test goed met volumes en tooling.
9.6 Problemen met executables in bind mounts (noexec, fs flags)
Als je projectmap op een filesystem staat met noexec (soms bij enterprise mounts), krijg je errors als:
permission deniedbij uitvoeren van scripts/binaries
Check mount options:
mount | grep " $(df -P . | tail -1 | awk '{print $6}') " -n
Oplossing: verplaats je project naar een exec-allowed filesystem of pas mountopties aan (admin).
10. Rootless Docker en “Docker socket permissies”
10.1 Waar is de socket?
Rootful:
/var/run/docker.sock(root-owned)
Rootless:
- meestal:
/run/user/1000/docker.sock
Check:
echo "$DOCKER_HOST"
ls -la /run/user/$(id -u)/docker.sock
Als DOCKER_HOST niet gezet is, kan docker proberen de rootful socket te gebruiken en falen (of per ongeluk rootful gebruiken).
Zet:
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
10.2 Tools die de socket hardcoden
Sommige tools verwachten /var/run/docker.sock. Oplossingen:
- Stel
DOCKER_HOSTin voor die tool. - Maak een symlink (niet altijd wenselijk; kan root vereisen in
/var/run). - Gebruik Docker contexts en zorg dat je tool contexts respecteert.
11. Systemd user service debuggen
11.1 Logs bekijken
journalctl --user -u docker -e
11.2 Veelvoorkomende issues
Issue: service start niet, XDG_RUNTIME_DIR ontbreekt
Dit gebeurt als je probeert te starten zonder user session of zonder linger.
Fix:
sudo loginctl enable-linger "$(whoami)"
systemctl --user daemon-reload
systemctl --user restart docker
Issue: newuidmap errors
Controleer:
- subuid/subgid entries
uidmappakket geïnstalleerd- kernel user namespaces toegestaan (sommige hardened systemen zetten dit uit)
Check sysctl (kan distro-afhankelijk zijn):
sysctl kernel.unprivileged_userns_clone 2>/dev/null || true
Als dit 0 is, heb je admin nodig om het aan te zetten (op Debian-achtige systemen vaak relevant):
sudo sysctl -w kernel.unprivileged_userns_clone=1
Permanent via /etc/sysctl.d/*.conf (admin).
12. Builden in rootless: BuildKit, caching en performance
Rootless Docker gebruikt doorgaans BuildKit. Check:
docker buildx version
docker buildx ls
Build een image:
cat > Dockerfile <<'EOF'
FROM alpine:3.20
RUN adduser -D app
USER app
WORKDIR /home/app
RUN echo "Hallo rootless" > msg.txt
CMD ["cat", "msg.txt"]
EOF
docker build -t rootless-demo .
docker run --rm rootless-demo
12.1 Cache en filesystem
Als je fuse-overlayfs gebruikt, kunnen builds trager zijn. Tips:
- minimaliseer layer count
- gebruik
.dockerignore - gebruik build cache mounts (BuildKit):
# syntax=docker/dockerfile:1.7
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
CMD ["npm","test"]
13. Rootless + Kubernetes tooling (kind, minikube) en nested containers
Veel Kubernetes dev tools starten “Docker-in-Docker” of verwachten privileged features.
- kind werkt soms met rootless, maar kan extra configuratie vereisen.
- DinD (Docker in Docker) is vaak problematisch rootless.
Alternatieven:
- gebruik
podmanrootless voor sommige workflows - gebruik een VM (bijv.
docker contextnaar een remote daemon) - gebruik
nerdctl+ rootless containerd (andere stack)
14. Praktische troubleshooting: checklist
14.1 “Docker werkt niet” (client kan daemon niet vinden)
Symptoom:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock
Fix:
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker info
Of:
docker context use rootless
14.2 “permission denied” bij bind mount schrijven
Symptoom:
- app kan geen files schrijven in gemounte map
Fix (dev):
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/work" -w /work alpine sh -c "touch ok"
In Compose: user: "${UID}:${GID}".
14.3 Bestanden met UID 100000 verschijnen in je project
Oorzaak: container root schrijft naar bind mount.
Fix:
- draai container als jouw UID/GID
- of gebruik named volumes
- of herstel ownership op host:
sudo chown -R "$(id -u):$(id -g)" .
Let op: als je geen sudo hebt, kun je ownership niet terugzetten; voorkom het door niet als container-root te schrijven.
14.4 Poort 80/443 werkt niet
Fix:
- gebruik 8080/8443
- of host reverse proxy
- of admin-level oplossing met capabilities (meestal niet de moeite waard)
14.5 “Operation not permitted” bij bepaalde syscalls/capabilities
Rootless containers hebben minder privileges. Workarounds:
- vermijd
--privileged(werkt rootless beperkt) - herontwerp: gebruik unprivileged alternatieven
- voor low-level networking: verwacht dat sommige tools niet werken (bijv. raw sockets)
15. Aanbevolen best practices
- Gebruik rootless voor developer machines en shared hosts waar je de security winst wil.
- Gebruik named volumes voor databases en stateful data.
- Bind mounts alleen voor source code, en draai de container met jouw UID/GID.
- Publiceer geen privileged ports; zet een reverse proxy op host of gebruik hogere poorten.
- Gebruik Docker contexts om rootless/rootful niet door elkaar te halen.
- Documenteer je setup (bijv.
.envmetUID/GIDvoor Compose).
16. Complete voorbeeldsetup (end-to-end)
16.1 Rootless installeren + service starten
# 1) subuid/subgid check (admin actie indien nodig)
cat /etc/subuid | grep "^$(whoami):" || echo "Geen subuid entry"
cat /etc/subgid | grep "^$(whoami):" || echo "Geen subgid entry"
# 2) rootless setup
dockerd-rootless-setuptool.sh install
# 3) start daemon (systemd user)
systemctl --user start docker
systemctl --user enable docker
# 4) zorg dat docker client de juiste socket gebruikt
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
# 5) test
docker run --rm hello-world
16.2 Dev container met correcte permissies
mkdir -p ~/rootless-demo && cd ~/rootless-demo
echo 'console.log("ok");' > index.js
Run Node met bind mount en jouw UID:
docker run --rm \
-u "$(id -u):$(id -g)" \
-v "$PWD:/app" -w /app \
node:22-alpine node index.js
Maak een file vanuit container:
docker run --rm \
-u "$(id -u):$(id -g)" \
-v "$PWD:/app" -w /app \
alpine sh -c 'echo "gemaakt" > gemaakt.txt && ls -ln gemaakt.txt'
Je ziet dat gemaakt.txt jouw UID/GID heeft.
17. Wanneer is rootless níet geschikt?
Rootless is niet “beter in alles”. Overweeg rootful of een VM als je:
- maximale netwerkperformance nodig hebt (heavy throughput/latency)
- afhankelijk bent van privileged features (
--privileged, kernel modules, low-level networking) - complexe cgroup limits en monitoring nodig hebt met gegarandeerde werking
- veel tooling hebt die hard
/var/run/docker.sockverwacht en lastig aan te passen is
Een alternatief patroon is: rootless op je workstation, maar rootful of managed runtime (Kubernetes, containerd) op servers.
18. Samenvatting
Rootless Docker is een sterke security- en usability-upgrade: geen root-daemon, minder risico’s, en vaak prima voor development en veel server-taken. De prijs is dat je rekening moet houden met:
- andere networking (slirp4netns, port forwarding)
- beperkingen op privileged ports en capabilities
- andere storage driver (fuse-overlayfs)
- vooral: UID/GID mapping die bind mounts en permissies beïnvloedt
Met de praktische strategieën uit deze tutorial—named volumes voor data, bind mounts met -u $(id -u):$(id -g), juiste DOCKER_HOST, en systemd user service debugging—kun je Rootless Docker stabiel en voorspelbaar inzetten.
Als je wil, kan ik ook:
- een diagnose-stappenplan maken op basis van jouw
docker info+journalctl --user -u dockeroutput - een voorbeeld
compose.yamlgeven voor een typische stack (nginx + app + db) die rootless-proof is - tips geven voor rootless op specifieke distro’s (Ubuntu LTS vs Fedora vs Arch) en cgroups v2 tuning