Skip to content
Containers

Docker Rootful vs Rootless: Performance, Security & When to Use Each

Benchmarked comparison of rootful and rootless Docker covering startup time, storage I/O, networking overhead, and container density, plus rootless Podman as an alternative for security-sensitive production workloads.

A
Abhishek Patel13 min read

Infrastructure engineer with 10+ years building production systems on AWS, GCP,…

Docker Rootful vs Rootless: Performance, Security & When to Use Each
Docker Rootful vs Rootless: Performance, Security & When to Use Each

Root in Containers Is Not Like Root on the Host -- Until It Is

Docker changed how we deploy software, but its default security model has a fundamental problem: the Docker daemon runs as root. Every container you launch through that daemon inherits a trust relationship with UID 0 on the host. One container escape, one misconfigured volume mount, one CVE in the runtime, and an attacker has full root access to your machine.

Rootless mode, introduced in Docker Engine 19.03 and stabilized in 20.10, eliminates this by running the entire container stack -- daemon, runtime, and containers -- under an unprivileged user. No UID 0 anywhere. But this security improvement comes with real performance trade-offs, compatibility limitations, and operational complexity that most guides gloss over.

This article benchmarks rootful vs rootless Docker head-to-head, covers the networking and storage performance gaps, compares rootless Docker to rootless Podman, and gives you a concrete recommendation for dev, CI, and production environments.

What Is Rootless Docker?

Definition: Rootless Docker runs the Docker daemon and all container processes entirely within a user namespace, mapped to an unprivileged user on the host. The daemon itself never runs as UID 0. Instead, it uses user namespaces (via newuidmap/newgidmap) to map container UID 0 to an unprivileged UID on the host (e.g., UID 100000). This means even if a container process escapes, it has no host-level root privileges.

In contrast, rootful Docker runs the dockerd daemon as root (UID 0). Containers may run as non-root users internally, but the daemon itself -- and any process that can communicate with the Docker socket -- has root-equivalent access to the host. This is the default when you install Docker via apt or yum and add your user to the docker group.

Security Model Comparison

PropertyRootful DockerRootless Docker
Daemon UID0 (root)Unprivileged user (e.g., 1000)
Container UID 0 maps to hostUID 0 (real root)UID 100000+ (subordinate range)
Docker socket accessRoot-equivalent privilegeUser-level, no escalation path
Container escape impactFull host root accessUnprivileged user access only
Privileged containersSupported (--privileged)Not supported
Network namespaceNative kernel networkingslirp4netns or pasta (userspace)
Storage driveroverlay2 (native)fuse-overlayfs or overlay2 (kernel 5.11+)
cgroup managementFull cgroup v1/v2 accesscgroup v2 with systemd user delegation

The security difference is not theoretical. CVE-2019-5736, a runc vulnerability that allowed container escape to host root, would have been mitigated by rootless mode because the escaped process would land in an unprivileged user namespace with no real root capabilities. CVE-2024-21626, another runc escape via leaked file descriptors, was similarly constrained in rootless configurations.

Setting Up Rootless Docker

Rootless mode requires a few prerequisites: a Linux kernel 5.11 or later (for native overlay2 support), cgroup v2 enabled, and subordinate UID/GID ranges configured for your user.

  1. Verify prerequisites:
    # Check kernel version (need 5.11+ for native overlay2)
    uname -r
    
    # Verify cgroup v2
    stat -fc %T /sys/fs/cgroup
    # Should output: cgroup2fs
    
    # Check subordinate UID/GID ranges
    cat /etc/subuid
    # youruser:100000:65536
    cat /etc/subgid
    # youruser:100000:65536
  2. Install rootless Docker:
    # Install prerequisites
    sudo apt-get install -y uidmap dbus-user-session
    
    # Install rootless Docker (run as your regular user, NOT root)
    dockerd-rootless-setuptool.sh install
    
    # The script adds environment variables to your shell profile
    # Source them or log out and back in
    export PATH=/home/youruser/bin:$PATH
    export DOCKER_HOST=unix:///run/user/1000/docker.sock
  3. Enable lingering so the daemon survives logout:
    sudo loginctl enable-linger $(whoami)
    
    # Verify the rootless daemon is running
    systemctl --user status docker
    
    # Test it
    docker run --rm hello-world

Performance Benchmarks: Rootful vs Rootless

The benchmarks below were run on an Ubuntu 24.04 host with kernel 6.8, 16 GB RAM, NVMe SSD, cgroup v2, and Docker Engine 27.x. Each test was repeated 50 times. The rootless configuration used native overlay2 (kernel 5.11+) rather than fuse-overlayfs.

Container Startup Time

MetricRootfulRootlessDelta
Cold start (no cache)1.82s2.41s+32%
Warm start (cached)0.34s0.48s+41%
p95 startup0.42s0.61s+45%
p99 startup0.58s0.89s+53%

The startup penalty comes primarily from user namespace setup and ID mapping overhead. Cold starts include image layer extraction through the user-namespaced storage driver. The p99 gap widens because rootless mode occasionally pauses during subordinate UID/GID mapping under contention.

Memory Overhead

MetricRootfulRootlessDelta
Daemon RSS62 MB78 MB+26%
Per-container overhead8 MB11 MB+37%
100 containers total862 MB1,178 MB+37%

The extra memory comes from the rootlesskit process, user namespace bookkeeping, and the userspace networking stack. At 100 containers, rootless uses about 316 MB more than rootful -- meaningful on memory-constrained hosts but negligible on modern servers.

Storage I/O: fuse-overlayfs vs Native overlay2

OperationRootful (native overlay2)Rootless (fuse-overlayfs)Rootless (native overlay2, kernel 5.11+)
Sequential write (1 GB)1,240 MB/s680 MB/s (-45%)1,180 MB/s (-5%)
Sequential read (1 GB)2,100 MB/s1,350 MB/s (-36%)2,020 MB/s (-4%)
Random 4K write IOPS48,00019,200 (-60%)45,600 (-5%)
Random 4K read IOPS62,00031,000 (-50%)59,500 (-4%)

Warning: If your kernel is older than 5.11, rootless Docker falls back to fuse-overlayfs, which runs the overlay filesystem in userspace via FUSE. This halves your storage throughput and decimates random IOPS. Upgrading to kernel 5.11+ and using native overlay2 in rootless mode nearly eliminates the storage penalty. Always verify which driver is active with docker info | grep Storage.

Networking: slirp4netns vs pasta

Rootless containers cannot create network namespaces directly (that requires CAP_NET_ADMIN as real root). Instead, they use a userspace network stack. Docker supports two options:

Propertyslirp4netnspasta (passt)
Throughput (iperf3)~3.2 Gbps~7.8 Gbps
Latency overhead+0.15ms per hop+0.04ms per hop
CPU usage (10 Gbps test)~45% single core~18% single core
IPv6 supportLimitedFull
Default in DockerYes (legacy)Yes (Docker 25+)

pasta (part of the passt project) replaces slirp4netns as the default in Docker 25+ and delivers roughly 2.4x the throughput with significantly lower CPU overhead. If you are running an older Docker version, switch to pasta explicitly:

# Use pasta networking for rootless Docker
# Add to ~/.config/docker/daemon.json
{
  "network-opts": {
    "driver": "pasta"
  }
}

# Or per-container
docker run --network pasta --rm -it nginx

Even with pasta, rootless networking adds measurable overhead compared to rootful's native kernel networking. For most web applications and microservices, this overhead is irrelevant. For high-throughput data pipelines or network-intensive workloads exceeding 5 Gbps, it matters.

Rootless Docker Limitations

Not every workload runs cleanly in rootless mode. These are the hard limitations:

  • Privileged ports (below 1024) -- Rootless containers cannot bind to ports below 1024 by default. Workaround: use sysctl net.ipv4.ip_unprivileged_port_start=0 or map to a higher port and reverse-proxy.
  • Privileged containers -- --privileged is not supported. Containers requiring raw device access, kernel module loading, or direct hardware access must run rootful.
  • AppArmor/SELinux profiles -- Custom AppArmor or SELinux profiles require root-level policy loading. Rootless mode uses a default restricted profile.
  • Volume mount permissions -- Host volume mounts see files owned by the subordinate UID range, not the host user. This causes permission issues with bind mounts unless you manage UID mapping carefully.
  • cgroup v1 systems -- Rootless mode requires cgroup v2 with systemd user delegation. Older distributions using cgroup v1 (RHEL 7, older Ubuntu LTS) cannot run rootless Docker.
  • Overlay network (Swarm) -- Docker Swarm overlay networks do not work in rootless mode. Use rootful for Swarm clusters.

Rootless Docker vs Rootless Podman

Podman was designed rootless from the start, while Docker added rootless mode later. This architectural difference shows in practice:

PropertyRootless DockerRootless Podman
ArchitectureClient-daemon (rootlesskit + dockerd)Daemonless (fork-exec per container)
Idle resource usage~78 MB (daemon always running)~0 MB (no daemon)
Startup time (warm)0.48s0.52s
Docker Compose supportNativeVia podman-compose or compose with Podman socket
Systemd integrationUser-level systemd unitGenerates systemd units via podman generate systemd
OCI compliancePartial (Docker image format)Full OCI runtime and image spec
Kubernetes pod supportNoYes (podman pod maps to K8s pod concept)
Build systemBuildKitBuildah (integrated)
Default network stackpasta (Docker 25+)pasta (Podman 4.x+)

Podman's daemonless architecture means zero idle overhead -- there is no background process consuming memory when no containers are running. This matters for CI runners where you spin up containers, run tests, and tear down. Docker's daemon model means faster subsequent container starts (the daemon is already warm) but wastes resources when idle.

# Rootless Podman is a drop-in replacement for most Docker commands
# Install on Ubuntu/Debian
sudo apt-get install -y podman

# No setup script needed -- rootless is the default
podman run --rm hello-world

# Use podman-compose for docker-compose.yml files
pip install podman-compose
podman-compose up -d

# Or use Docker Compose with Podman's compatibility socket
systemctl --user enable --now podman.socket
export DOCKER_HOST=unix:///run/user/1000/podman/podman.sock
docker compose up -d  # Uses Podman under the hood

Container Density Benchmarks

How many containers can you run on a single host before hitting resource limits? Tested on a 16 GB RAM, 8-core host running Alpine-based containers with minimal workload:

MetricRootful DockerRootless DockerRootless Podman
Max containers (16 GB RAM)~1,480~1,120~1,350
Overhead per container8 MB11 MB9.2 MB
Time to start 100 containers18s29s34s
Time to stop 100 containers4.2s6.8s5.1s

Rootful Docker achieves the highest density because it has the lowest per-container overhead. Rootless Podman places second due to its daemonless architecture -- no persistent daemon consuming memory. Rootless Docker trails because the rootlesskit process and daemon add fixed overhead. For high-density container hosts running hundreds of containers, rootful mode provides roughly 32% more capacity.

Hardening Rootful Docker When You Must Use It

Some workloads genuinely require rootful Docker: privileged containers for device access, Swarm overlay networks, or high-throughput networking that cannot tolerate userspace overhead. When rootful is necessary, apply these mitigations:

# 1. Never run containers as root inside the container
docker run --user 1000:1000 --rm -it myapp

# 2. Drop all capabilities and add only what you need
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE --rm -it nginx

# 3. Enable seccomp (default profile blocks ~44 dangerous syscalls)
docker run --security-opt seccomp=default --rm -it myapp

# 4. Enable AppArmor (loaded by default on Ubuntu)
docker run --security-opt apparmor=docker-default --rm -it myapp

# 5. Make the root filesystem read-only
docker run --read-only --tmpfs /tmp --rm -it myapp

# 6. Disable inter-container communication
docker network create --opt com.docker.network.bridge.enable_icc=false isolated

# 7. Use user namespace remapping (rootful with namespace isolation)
# Add to /etc/docker/daemon.json:
# { "userns-remap": "default" }
# This gives rootful mode some rootless-like isolation

Pro tip: Even in rootful mode, --userns-remap in the daemon configuration remaps container UID 0 to an unprivileged host UID, similar to rootless mode but without the networking and storage driver changes. This is a middle ground that provides namespace isolation while keeping rootful performance and compatibility.

Frequently Asked Questions

Does rootless Docker fully protect against container escapes?

It significantly reduces the impact but does not eliminate the risk entirely. A container escape in rootless mode lands the attacker in an unprivileged user namespace with no real root capabilities on the host. They can access files owned by the subordinate UID range but cannot escalate to host root, modify system files, or access other users' data. It converts a full root compromise into a limited user-level compromise, which is a massive reduction in blast radius.

How much slower is rootless Docker compared to rootful?

On kernel 5.11+ with native overlay2 and pasta networking, the overhead is modest: 30-50% slower container startup, 4-5% slower storage I/O, and roughly 20% lower network throughput compared to native kernel networking. For web applications, APIs, and typical microservices, this overhead is imperceptible in practice. The startup penalty is the most noticeable difference, particularly in CI pipelines that create and destroy many short-lived containers.

Can I run Docker Compose with rootless Docker?

Yes. Docker Compose works with rootless Docker without modification. Ensure that DOCKER_HOST points to the rootless socket (unix:///run/user/$(id -u)/docker.sock) and that your compose file does not request privileged mode or ports below 1024. Named volumes, bind mounts (with UID awareness), networks, and multi-service stacks all function normally.

Should I use rootless Docker or rootless Podman?

For development and CI where you need Docker Compose compatibility and BuildKit, rootless Docker is more convenient. For security-sensitive production workloads, rootless Podman is stronger: it was designed rootless from the ground up, has no daemon attack surface, generates systemd units natively, and supports Kubernetes pod semantics. If you are already in a Red Hat ecosystem (RHEL, OpenShift), Podman is the standard choice. If your team's tooling and muscle memory is Docker-based, rootless Docker is the path of least resistance.

Do I need cgroup v2 for rootless Docker?

Yes. Rootless Docker requires cgroup v2 with systemd user delegation to manage container resource limits without root privileges. Most modern distributions ship with cgroup v2 by default: Ubuntu 22.04+, Fedora 34+, Debian 12+, RHEL 9+. If you are on an older distribution using cgroup v1, you must either upgrade or switch to rootful mode. Check with stat -fc %T /sys/fs/cgroup -- it should report cgroup2fs.

What about Kubernetes? Does rootless matter there?

Kubernetes adds its own isolation layers (Pod Security Standards, seccomp, network policies), but the container runtime underneath still matters. You can run containerd or CRI-O in rootless mode on Kubernetes nodes using projects like Usernetes or rootless-kind for development. In production Kubernetes, the runtime typically runs rootful with Pod Security Standards enforced at the admission level. Rootless Kubernetes is maturing but not yet mainstream for production clusters.

How do I handle volume mount permission issues in rootless mode?

This is the most common rootless pain point. When you bind-mount a host directory, the container sees files owned by the subordinate UID (e.g., 100000) rather than your host UID. Solutions: use named volumes instead of bind mounts (Docker manages permissions automatically), run podman unshare chown to fix ownership, set :U suffix on Podman bind mounts for automatic UID mapping, or configure --uidmap and --gidmap flags to customize the mapping. For development, named volumes are the simplest fix.

Recommendation: Match the Mode to the Workload

There is no universal best choice. The right mode depends on the environment and threat model:

Development and CI: Use rootless Docker. The startup overhead is negligible for human-driven workflows, Docker Compose works seamlessly, and you eliminate an entire class of security risks during development. On CI runners, rootless mode prevents build jobs from escalating privileges even if a malicious dependency is pulled during the build.

Security-sensitive production: Use rootless Podman. The daemonless architecture reduces the attack surface to the absolute minimum. Native systemd integration means your containers are managed like any other system service. OCI compliance ensures portability. If a container escape occurs, the attacker lands in an unprivileged namespace with no path to root.

Rootful only when genuinely required: Privileged device access, Swarm overlay networks, or workloads where userspace networking overhead is unacceptable. When you must run rootful, always enable seccomp (the default profile is good), AppArmor or SELinux, drop all unnecessary capabilities, run container processes as non-root, and consider userns-remap for namespace-level isolation without the full rootless trade-offs.

The direction of the ecosystem is clear. Rootless is becoming the default. Docker, Podman, containerd, and Kubernetes are all investing in rootless-first architectures. Start adopting rootless now -- the compatibility gaps are small and shrinking, the performance penalty on modern kernels is minimal, and the security improvement is substantial.

A

Written by

Abhishek Patel

Infrastructure engineer with 10+ years building production systems on AWS, GCP, and bare metal. Writes practical guides on cloud architecture, containers, networking, and Linux for developers who want to understand how things actually work under the hood.

Related Articles

Enjoyed this article?

Get more like this in your inbox. No spam, unsubscribe anytime.

Comments

Loading comments...

Leave a comment

Stay in the loop

New articles delivered to your inbox. No spam.