Suggest an editImprove this articleRefine the answer for “What is rootless Docker and when to use it?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Rootless Docker** runs `dockerd` as a non-root user via Linux user namespaces. The daemon, the containers, all run unprivileged on the host. A container escape gives the attacker the unprivileged user's permissions, not host root. ```bash curl -fsSL https://get.docker.com/rootless | sh export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock docker run --rm hello-world ``` **Key:** big security win — no root daemon to abuse. Trade-offs: no privileged containers, slower network (slirp4netns), no system services, ~20% I/O overhead. Use it for security-sensitive multi-tenant hosts, HPC clusters, CI runners; not where you need full Docker capabilities.Shown above the full answer for quick recall.Answer (EN)Image**Rootless Docker** runs the entire Docker stack as an unprivileged user. The daemon does not need root; neither do the containers. It is the strongest security hardening you can apply to a Docker installation, paid for with some operational trade-offs. ## Theory ### TL;DR - Standard Docker: `dockerd` runs as **root**. Container processes are constrained but the daemon itself is privileged. - Rootless Docker: `dockerd` runs as **a regular user** via Linux user namespaces. Whole stack is unprivileged on the host. - A container escape from rootless Docker = compromise of the user, not host root. Big difference for shared infrastructure. - **Trade-offs:** no `--privileged` containers, no port binding below 1024 by default, slower network via slirp4netns, ~20% storage I/O overhead, no `docker run --network host` for the host. - **Use when:** security-sensitive multi-tenant hosts, CI runners, HPC clusters, regulated environments. **Skip when:** need privileged containers, very high I/O workloads, kernel modules. ### How it works Rootless Docker uses **user namespaces** to remap UIDs: ``` Host: Inside rootless container: user 'me' (UID 1000) root (UID 0) ↑ ↓ +- runs dockerd +- inside this user namespace, which uses subuids 100000-165535 'root' is a local construct ↓ that maps to host UID 100000 +- containers run as +- escape gives access to UIDs 100000-165535 UID 100000, NOT host root ``` The kernel's user namespace gives each container its own UID 0 (root inside) that maps to a different, unprivileged UID outside. Daemon runs as the regular user (no UID 0 needed on host). ### Installing rootless Docker ```bash # Install (does not need sudo for the rootless install itself) curl -fsSL https://get.docker.com/rootless | sh # The installer prints instructions; key parts: export PATH=$HOME/bin:$PATH export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock # Start the daemon systemctl --user start docker # (or `dockerd-rootless.sh &` directly) docker run --rm hello-world ``` The `DOCKER_HOST` env var points to the user's daemon socket, separate from any system Docker. ### Trade-offs #### What rootless gives up ```bash # Cannot bind to low ports without setcap (system-wide) docker run -p 80:80 nginx # "permission denied" by default # Workaround: use a higher port (8080), or grant CAP_NET_BIND_SERVICE # No --privileged or capability additions beyond user limits docker run --privileged ... # fails # Slower network (slirp4netns userland TCP/IP stack) # Throughput: ~half of bridge networking # Slower storage (overlay2 needs special setup; often falls back to fuse-overlayfs) # I/O: ~20% slower than rootful # No system services / no host network docker run --network host ... # fails ``` Most user-space workloads (web servers on high ports, app containers, language runtimes) work fine. Privileged or kernel-level work breaks. #### What rootless gains - **No root daemon to attack.** The single biggest privilege-escalation surface in classical Docker is gone. - **Per-user isolation.** Two users on the same host run separate Docker stacks; one cannot see the other's containers. - **No `docker` group risk.** The classical "add me to docker group" = root warning does not apply; the rootless daemon is bound to the user. - **Easier audit story.** "What can this Docker do?" answer: "whatever its user can." ### Comparison with Podman Podman is daemonless and rootless by default. The two solutions overlap: | | Rootless Docker | Podman | |---|---|---| | Daemon | yes (per-user dockerd) | no (each `podman` is its own process) | | Rootless | yes (opt-in install) | yes (default) | | Compose support | yes (docker compose) | yes (podman compose) | | Image format | OCI | OCI | | Maturity | mature | mature | | Linux distro defaults | Docker is the de-facto standard | Podman is default on RHEL/Fedora | For security-first deployments on Red Hat-based systems, Podman is the path of least resistance. For mixed Docker/legacy compatibility, rootless Docker. ### Setting up port bindings below 1024 ```bash # Grant the rootlesskit binary capability to bind low ports sudo setcap cap_net_bind_service=ep $(which rootlesskit) # Or per-container, less common ``` This is the most common operational hurdle. Once set up, `-p 80:80` works. ### Common mistakes **Mixing rootless and rootful daemons** ```bash # Both could be running sudo systemctl status docker # system-wide rootful systemctl --user status docker # user-level rootless ``` If both are running, `docker` commands go to whichever `DOCKER_HOST` points at. Confusing. Pick one per machine. **Trying `--privileged` and getting unhelpful errors** ```bash $ docker run --privileged ubuntu mount mount: /proc/sys: must be mounted on /proc/sys ``` Rootless cannot grant `--privileged`. If your app needs it, rootless is not for you. **Forgetting that the network is slower** For most apps, slirp4netns is fine (typical web traffic). For high-throughput services, benchmark before committing. **Filesystem permission surprises** ```bash $ docker run -v /home/me/data:/data myapp # Inside: files appear owned by 'nobody' or weird UIDs ``` The rootless daemon's UID-mapping affects file ownership. `-v` bind mounts may show unfamiliar ownership inside the container. Plan UIDs accordingly. ### Real-world usage - **CI runners:** GitHub Actions self-hosted runners often use rootless Docker — multiple users on one host, no escape to host root. - **HPC clusters:** scientific computing on shared nodes; rootless gives each user their own Docker without sysadmin grant. - **Per-tenant container hosts:** multi-tenant servers where each tenant runs their own dockerd unprivileged. - **Regulated environments:** finance, defense, healthcare — auditors love "no root daemon". - **Developer laptops on cooperative machines:** if the laptop is shared (rare these days but exists), rootless prevents one user's container from owning the host. ### Follow-up questions **Q:** Does rootless Docker work on macOS / Windows? **A:** Docker Desktop on Mac/Windows already runs the daemon inside a Linux VM, isolating it from your host OS. Functionally similar security to rootless on Linux. The rootless terminology is for Linux-host scenarios. **Q:** Is rootless Docker production-ready? **A:** Yes, since around 2020. Used at scale by GitHub, Red Hat (via Podman), various HPC sites. Mature enough to be the default for security-conscious deployments. **Q:** What is `slirp4netns`? **A:** A userspace TCP/IP stack that gives unprivileged containers network connectivity. Slower than kernel networking (because it implements TCP/IP in userspace), but does not require root. There is also `vpnkit` (used by Docker Desktop) and `pasta` (newer, faster). **Q:** Can I run rootless Docker alongside rootful Docker on the same host? **A:** Yes, but they are separate. Different sockets (`/var/run/docker.sock` vs `$XDG_RUNTIME_DIR/docker.sock`), different containers, different networks. `DOCKER_HOST` chooses which one your CLI talks to. **Q:** (Senior) When does the rootless trade-off NOT make sense? **A:** When your workload demands kernel features rootless cannot provide: device pass-through, kernel module loading, low-level network manipulation (raw sockets, eBPF), real `--privileged` for nested virtualization, or extremely I/O-bound workloads where the 20% overhead is unacceptable. For these, accept rootful Docker and harden it via other means (seccomp, AppArmor, user namespace remapping at the daemon level via `userns-remap`). ## Examples ### Side-by-side: same docker run, different security model ```bash # Rootful Docker (default) sudo systemctl start docker docker run --rm alpine ps -ef # UID 0 inside, dockerd as root on host, container escape = host root # Rootless Docker systemctl --user start docker export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock docker run --rm alpine ps -ef # UID 0 inside, dockerd as user on host, container escape = unprivileged user ``` Same command, very different blast radius. ### CI runner setup ```bash # As the runner user curl -fsSL https://get.docker.com/rootless | sh # Persist env cat >> ~/.bashrc <<'EOF' export PATH=$HOME/bin:$PATH export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock EOF # Auto-start on login (lingering keeps service running between SSH sessions) sudo loginctl enable-linger runner systemctl --user enable docker systemctl --user start docker ``` Now each runner instance has its own Docker stack, no shared root daemon. ### Verifying rootless ```bash $ docker info | grep -i 'rootless\|cgroup' rootless Cgroup Driver: cgroupfs $ ps -ef | grep dockerd runner 12345 1 /home/runner/bin/dockerd-rootless.sh # Note: NOT running as root $ docker run --rm alpine cat /proc/self/status | grep CapEff CapEff: 0000003fffffffff # Capabilities still constrained to user's allowance ``` The daemon process belongs to `runner`, not `root`. Container capabilities are bounded by the user's host capabilities.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.