← Terug naar tutorials

Rootless Docker: installatie, beperkingen en permissieproblemen oplossen

rootless dockerdockerlinuxcontainerssecuritypermissionsuid gid mappinguser namespacescgroupsnetworking

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:

Doelgroep: Linux-gebruikers die Docker willen draaien zonder sudo en 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

1.2 Waarom rootless?

1.3 Hoe maakt Docker dit mogelijk?

Rootless Docker leunt op:


2. Vereisten en checks vooraf

2.1 Kernel en distro

Rootless werkt op moderne Linux-kernels (meestal 5.x+ is prima). Je hebt nodig:

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:

sudo apt-get update
sudo apt-get install -y uidmap
sudo dnf install -y shadow-utils
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:

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:


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:

Gevolgen:

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:

  1. Gebruik een hogere poort (simpelst):
docker run --rm -p 8080:80 nginx:alpine
  1. Gebruik authbind (niet overal beschikbaar) of setcap op rootlesskit (geavanceerd, distro-afhankelijk, vaak niet wenselijk).

  2. 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:

docker run -d --name web -p 8080:80 nginx:alpine
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:

sudo apt-get install -y fuse-overlayfs
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:

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


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:

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:

Rootless:

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:

  1. Stel DOCKER_HOST in voor die tool.
  2. Maak een symlink (niet altijd wenselijk; kan root vereisen in /var/run).
  3. 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:

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:

# 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.

Alternatieven:


14. Praktische troubleshooting: checklist

14.1 “Docker werkt niet” (client kan daemon niet vinden)

Symptoom:

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:

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:

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:

14.5 “Operation not permitted” bij bepaalde syscalls/capabilities

Rootless containers hebben minder privileges. Workarounds:


15. Aanbevolen best practices

  1. Gebruik rootless voor developer machines en shared hosts waar je de security winst wil.
  2. Gebruik named volumes voor databases en stateful data.
  3. Bind mounts alleen voor source code, en draai de container met jouw UID/GID.
  4. Publiceer geen privileged ports; zet een reverse proxy op host of gebruik hogere poorten.
  5. Gebruik Docker contexts om rootless/rootful niet door elkaar te halen.
  6. Documenteer je setup (bijv. .env met UID/GID voor 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:

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:

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: