Rootless Docker and Its Hidden Security Trade-Offs

speckx1 pts0 comments

Rootless Docker and Its Hidden Security Trade-Offs - Ken Muse

Ken Muse

This is a post in the series<br>Docker Privilege . The posts in this series include:<br>Apr 9, 2026 -<br>Building Container Isolation From the Linux Kernel Up<br>Apr 16, 2026 -<br>How Docker Uses Root Privileges<br>Apr 23, 2026 -<br>Rootless Docker and Its Hidden Security Trade-Offs

In the<br>previous post, you traced the path used by the Docker daemon through the socket, image builds, and BuildKit. All of these steps required root. The security concern is straightforward: the daemon runs as root and the socket at /var/run/docker.sock grants full access to anyone in the docker group. If the daemon has a vulnerability or an attacker escapes a container, they suddenly have root privileges on the host.<br>This post explores the alternative: rootless Docker. You&rsquo;ll see how it works under the hood, what it genuinely improves, and why this solution introduces its own set of security trade-offs.<br>How rootless Docker works<br>Rootless Docker addresses the root-daemon risk by running dockerd itself as an unprivileged user. Each user who wants Docker runs their own personal daemon process, started under their own account. The socket is also per-user, typically at $XDG_RUNTIME_DIR/docker.sock (a per-user directory such as /run/user/1000/docker.sock) rather than /var/run/docker.sock.<br>That daemon still needs to create PID (Process ID), mount, and network namespaces – operations that require CAP_SYS_ADMIN, as you saw in the<br>first post. How does an unprivileged daemon pull this off? It takes advantage of a special namespace you saw in the previous post.<br>The user namespace is the one namespace type you can create without root. You previously saw that creating a user namespace without proper UID mapping lands you as &ldquo;nobody.&rdquo; Rootless Docker uses a helper called RootlessKit to solve this. RootlessKit bootstraps a user namespace and uses helpers to configure UID (User ID) mappings so the process inside appears to be root. These helper programs – newuidmap and newgidmap – are installed with the setuid bit. This grants them permission to temporarily run an executable with the permissions of the file&rsquo;s owner – typically root – instead of the calling user. With those privileges granted, the daemon can create all the nested namespaces it needs, because the kernel permits nested namespace creation when a user namespace is the outermost boundary.<br>This architecture meaningfully limits the damage that a compromise can cause:<br>If the daemon is exploited, the attacker gets the privileges of the unprivileged user – not root on the host.<br>If a container escape occurs, the escaped process lands inside the user namespace as UID 0. This UID is mapped to a non-root UID on the host. That means it can&rsquo;t install kernel modules, modify system files, or access other users&rsquo; data.<br>The environment is mostly isolated from other processes<br>For these to work, the unprivileged user must not be able to alter the mappings since that would allow it to create processes that are mapped to root and gain those privileges.<br>How rootless BuildKit works<br>BuildKit, Docker&rsquo;s modern build engine, can also operate in rootless mode using the same approach. RootlessKit bootstraps a user namespace, sets up UID mappings, and then starts buildkitd inside that namespace. From there, buildkitd can create the child PID, mount, and UTS (hostname) namespaces it needs for each build step.<br>A key part of this is UID mapping. When rootless BuildKit runs a RUN step, that step appears to execute as UID 0 (root) inside its container. But that UID 0 is actually mapped to the unprivileged host user&rsquo;s UID through the user namespace. For example, if your host UID is 1000, the kernel maps UID 0 in the namespace to 1000 on the host, and UIDs 1 through 65535 map to a range defined in /etc/subuid. Files the build creates appear owned by root inside the container, but are actually owned by a non-root user on the host.<br>This means that the account itself isn&rsquo;t actually running as root, so it also isn&rsquo;t able to access the host system&rsquo;s privileged APIs. It can access many of the same interfaces to create namespaces, but it does so through the user namespace, which limits the scope of its access.<br>The limitations of rootless mode<br>Rootless mode comes with practical limitations. These apply to both rootless Docker and rootless BuildKit:<br>The host kernel must permit unprivileged user namespace creation (controlled by kernel.unprivileged_userns_clone, or AppArmor&rsquo;s userns restriction on Ubuntu 24.04 and later). Many newer distributions often have this enabled by default to support browsers and containers. In 2025, Kubernetes<br>enabled userns by default as well. Some distributions or hardened configurations may restrict this.<br>When running BuildKit inside a container (for example in a Kubernetes pod), the container needs seccomp=unconfined.<br>On Ubuntu 24.04 and later, AppArmor restricts unprivileged...

user docker root rootless namespace rsquo

Related Articles