← Back to Tutorials

Rootless Docker: Setup, Limitations & Fixing Permission Errors

rootless dockerdocker securitylinux containersuser namespacessubuid subgidpermission deniedfuse-overlayfsslirp4netnscgroups v2docker troubleshooting

Rootless Docker: Setup, Limitations & Fixing Permission Errors

Rootless Docker lets you run the Docker daemon and containers as a non-root user, reducing the blast radius of daemon or container escapes and making Docker feasible in locked-down environments (shared servers, corporate laptops, CI runners without privileged access). This tutorial walks through installation, how it works, common limitations, and—most importantly—how to fix the permission errors that show up when switching from “rootful” Docker.


Table of Contents


What “Rootless Docker” Actually Means

In traditional (rootful) Docker:

In rootless Docker:

The core benefit: even if a container breaks out, it typically lands in your user’s permissions, not host root.


Prerequisites and Concepts

User namespaces and subordinate IDs

Rootless containers rely on user namespaces. The kernel maps container UIDs/GIDs to a range of “subordinate” IDs on the host.

You need entries like:

This says: “map container IDs into host IDs starting at 100000, for 65536 IDs”.

Also required: newuidmap and newgidmap (from the uidmap package on many distros). These helpers set up the mappings safely.

Rootless networking (slirp4netns / vpnkit)

Rootless Docker typically cannot create privileged network namespaces and iptables rules the same way rootful Docker does. Instead it uses user-space networking:

This affects performance and features (e.g., no true L2 integration, different port-forwarding behavior).

Storage drivers (fuse-overlayfs vs vfs)

Rootless Docker often uses:

Your storage driver impacts performance and compatibility.


Install Rootless Docker (Linux)

The official Docker packages include a rootless setup tool. The exact package names differ by distro, but the flow is consistent.

1) Remove conflicts / understand coexistence

You can keep rootful Docker installed and also run rootless Docker for your user. They are separate daemons with separate sockets.

Check if rootful Docker is installed/running:

systemctl status docker
docker info 2>/dev/null | head

If you want to stop rootful Docker (optional):

sudo systemctl disable --now docker.service docker.socket

2) Install dependencies

On Debian/Ubuntu:

sudo apt-get update
sudo apt-get install -y uidmap dbus-user-session slirp4netns fuse-overlayfs

On Fedora:

sudo dnf install -y uidmap slirp4netns fuse-overlayfs

On Arch:

sudo pacman -S --needed uidmap slirp4netns fuse-overlayfs

Install Docker Engine (example for Debian/Ubuntu using Docker’s repo is omitted here for brevity), then confirm you have the rootless tool:

dockerd-rootless-setuptool.sh --help

If that script isn’t found, your Docker package may be incomplete or installed via a method that doesn’t ship it.

3) Configure /etc/subuid and /etc/subgid

Check current mappings:

grep -E "^$USER:" /etc/subuid /etc/subgid

If nothing is returned, create mappings (choose a free range; 100000 is common):

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$USER"

Verify:

grep -E "^$USER:" /etc/subuid
grep -E "^$USER:" /etc/subgid

Log out and back in (or reboot) so your session picks up changes cleanly.

4) Run the rootless setup tool

Run as your normal user (no sudo):

dockerd-rootless-setuptool.sh install

Typical output includes:

If your system uses systemd, the tool usually enables a user service:

systemctl --user status docker

Start it if needed:

systemctl --user start docker
systemctl --user enable docker

Enable “linger” if you want Docker to run even when you’re not logged in:

sudo loginctl enable-linger "$USER"

5) Configure environment variables

Rootless Docker uses a different socket path. Commonly:

Set DOCKER_HOST accordingly. The setup tool often suggests:

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

To make it persistent, add to ~/.bashrc or ~/.profile:

echo 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' >> ~/.bashrc

Reload:

source ~/.bashrc

6) Validate the installation

Check that your client is talking to the rootless daemon:

docker context ls
docker info

In docker info, look for hints like:

Run a test container:

docker run --rm hello-world
docker run --rm -it alpine sh -lc 'id && echo OK'

Inside the container, id may show uid=0(root) but that “root” is mapped to your unprivileged host IDs.


Using Rootless Docker Day-to-Day

Switching between rootless and rootful contexts

If you have both daemons, use Docker contexts to avoid constantly exporting DOCKER_HOST.

Create a context for rootless (adjust socket path):

docker context create rootless --docker "host=unix:///run/user/$(id -u)/docker.sock"

Rootful usually uses:

docker context create rootful --docker "host=unix:///var/run/docker.sock"

Switch:

docker context use rootless
docker info | grep -i rootless -n || true

docker context use rootful
docker info | head

Docker Compose with rootless

Compose uses the same Docker socket as the docker CLI. If DOCKER_HOST or context is correct, Compose “just works”:

docker compose up -d
docker compose ps

If you see socket permission errors, jump to the socket troubleshooting section below.


Networking in Rootless Mode

Port publishing: why low ports fail

In Linux, binding to ports <1024 is privileged. Rootless Docker cannot bind directly to :80 or :443 without help.

Example failure:

docker run --rm -p 80:80 nginx

You may see errors like “permission denied” or “cannot bind”.

Fix: allow binding to privileged ports

Option A: Use a higher host port (simplest):

docker run --rm -p 8080:80 nginx

Option B: Allow unprivileged port binding via sysctl:

sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0

Persist it:

echo 'net.ipv4.ip_unprivileged_port_start=0' | sudo tee /etc/sysctl.d/99-rootless-docker.conf
sudo sysctl --system

Now try:

docker run --rm -p 80:80 nginx

Option C: Use a reverse proxy on the host (rootful service) forwarding to a high port. This keeps the kernel default intact.

Host networking and why it’s different

--network host is fundamentally a “share host namespace” feature. In rootless mode, networking is already user-space mediated; host networking may not behave identically or may be unsupported depending on your setup.

Prefer bridge networking and explicit port publishing:

docker run --rm -p 127.0.0.1:8080:80 nginx

Binding to 127.0.0.1 is often desirable on developer machines.


Storage & Filesystem Behavior

Where rootless Docker stores data

Rootful Docker stores data under /var/lib/docker. Rootless Docker stores under your home/user directories, commonly:

Inspect:

docker info | grep -E 'Docker Root Dir|Storage Driver'

You can also see disk usage:

docker system df
du -sh ~/.local/share/docker 2>/dev/null || true

OverlayFS constraints and fuse-overlayfs

Rootless can’t always use kernel overlay2 due to permission constraints. fuse-overlayfs provides an overlay filesystem in user space.

Check the driver:

docker info | grep -i 'Storage Driver'

If you see vfs, performance may be poor. Installing fuse-overlayfs and ensuring it’s available often improves this.


Fixing Common Permission Errors (with Real Causes)

This section is the practical core: error messages, what they mean in rootless mode, and how to fix them.

permission denied on the Docker socket

Symptoms:

docker ps
# Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock

Cause: Your client is trying to use the rootful socket (/var/run/docker.sock) but you’re not in the docker group (or root). In rootless mode you should be using your user socket.

Fix 1: Point to the rootless socket:

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker ps

Fix 2: Use contexts:

docker context use rootless
docker ps

Fix 3: Confirm the rootless daemon is running:

systemctl --user status docker
systemctl --user start docker

Also confirm the socket exists:

ls -l /run/user/$(id -u)/docker.sock

If the socket exists but permissions look wrong, restart the user service:

systemctl --user restart docker

cannot create /var/run/docker.sock: no such file or directory

Symptoms appear when scripts hardcode /var/run/docker.sock.

Cause: Rootless Docker does not create /var/run/docker.sock.

Fix: Update scripts/CI to use the rootless socket:

Example: run a container that uses the host Docker daemon (rootless):

docker run --rm -it \
  -e DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock \
  -v /run/user/$(id -u)/docker.sock:/run/user/$(id -u)/docker.sock \
  alpine sh

Inside that container, you’d also need the Docker CLI binary if you want to run docker commands.

newuidmap / newgidmap errors

Common messages:

Causes and fixes:

  1. Missing uidmap tools:
which newuidmap newgidmap

If not found, install:

  1. Missing subordinate ID ranges:
grep "^$USER:" /etc/subuid
grep "^$USER:" /etc/subgid

If empty, add:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$USER"

Then log out/in and rerun:

dockerd-rootless-setuptool.sh install
  1. Kernel/user namespace restrictions (common in hardened environments)

Check:

sysctl kernel.unprivileged_userns_clone

If 0, rootless containers may be blocked. You can enable (if policy allows):

sudo sysctl -w kernel.unprivileged_userns_clone=1
echo 'kernel.unprivileged_userns_clone=1' | sudo tee /etc/sysctl.d/99-userns.conf
sudo sysctl --system

Note: Some distros enable user namespaces by default; others restrict them for security policy reasons.

write /proc/...: permission denied and missing capabilities

Symptoms: a container tries to change kernel parameters, mount filesystems, or use privileged operations.

Examples:

Cause: Rootless containers have fewer effective capabilities on the host. Even if the container process is “root” inside its namespace, it’s not host-root.

Fix options:

Example: if an image expects to run sysctl -w, consider setting sysctls at the host level (if appropriate) or using a rootful environment.

Bind mounts: permission denied / wrong ownership inside container

This is one of the most confusing rootless issues.

Symptom A: Container can’t write to a bind mount

Example:

mkdir -p ~/appdata
chmod 700 ~/appdata

docker run --rm -it -v ~/appdata:/data alpine sh -lc 'touch /data/x'
# touch: /data/x: Permission denied

Cause: User namespace mapping means container “root” is not host root; file permissions on the host still apply. If the host directory is not writable by your user (or by the mapped UID), writes fail.

Fix: Make the directory writable by your actual user:

chmod u+rwx ~/appdata

Or use a directory you own with appropriate permissions:

mkdir -p ~/containers/appdata
chown "$USER:$USER" ~/containers/appdata
chmod 755 ~/containers/appdata

Symptom B: Files created by container appear with strange UID/GID on host

You might see files owned by 100000 or similar.

Cause: That’s the subordinate ID mapping at work.

Fix options:

Example:

docker run --rm -it \
  -u $(id -u):$(id -g) \
  -v "$PWD":/work \
  -w /work \
  alpine sh -lc 'touch created_by_me'
ls -ln created_by_me

This is a strong default for developer workflows.

Symptom C: You need “real root” ownership semantics

If a workload requires writing files as host root, rootless Docker is the wrong tool. Use rootful Docker, a VM, or adjust the workflow.

Volumes and SELinux/AppArmor gotchas

On SELinux systems, bind mounts may fail even when UNIX permissions look correct.

Symptoms can look like:

Fix: Apply SELinux labels on bind mounts (rootful Docker often uses :Z/:z). Rootless support varies by distro and configuration, but you can try:

docker run --rm -it -v "$PWD":/work:Z alpine sh

If labeling isn’t possible in your environment, prefer Docker-managed volumes (which live under your rootless Docker data dir) rather than arbitrary host paths.

AppArmor can also restrict operations; check dmesg/audit logs if you suspect MAC policy interference.

“Operation not permitted” when using ping, iptables, or raw sockets

Rootless containers generally cannot:

Example:

docker run --rm alpine sh -lc 'apk add --no-cache iputils && ping -c1 1.1.1.1'
# ping: permission denied (are you root?)
# or: Operation not permitted

Fix options:

Systemd user service issues

If Docker works only when you’re logged in, or the daemon stops after logout:

Check:

loginctl show-user "$USER" | grep -i linger
systemctl --user status docker

Enable lingering:

sudo loginctl enable-linger "$USER"

If the user service can’t connect to the session bus, ensure dbus-user-session is installed (Debian/Ubuntu) and that your systemd user instance is functional.


Security Notes and Trade-offs

Rootless Docker improves safety, but it’s not “perfect isolation”:

Practical guidance:


Troubleshooting Checklist

Run these commands and interpret results:

  1. Which daemon am I talking to?
docker context show
echo "$DOCKER_HOST"
docker info | sed -n '1,40p'
  1. Is the rootless daemon running?
systemctl --user status docker
ps aux | grep -E 'dockerd|rootless' | grep -v grep
  1. Does the socket exist and is it accessible?
ls -l /run/user/$(id -u)/docker.sock
  1. Do I have subuid/subgid configured?
grep "^$USER:" /etc/subuid
grep "^$USER:" /etc/subgid
  1. Are user namespaces allowed?
sysctl kernel.unprivileged_userns_clone
  1. What storage driver is used?
docker info | grep -i 'Storage Driver'
  1. Port binding issues?
sysctl net.ipv4.ip_unprivileged_port_start

Uninstall / Reset Rootless Docker

To remove the user service and configuration:

dockerd-rootless-setuptool.sh uninstall

Stop and disable the user service:

systemctl --user disable --now docker

Remove rootless Docker data (this deletes images/containers/volumes for rootless daemon):

rm -rf ~/.local/share/docker
rm -rf ~/.docker

If you set environment variables in ~/.bashrc, remove them:

grep -n 'DOCKER_HOST=unix:///run/user' ~/.bashrc
# edit file and remove the line

Summary: When Rootless Docker Is the Right Choice

Rootless Docker is ideal when:

If you routinely need:

…then rootful Docker or a VM-based approach will be more compatible.


If you share a specific error message and your distro/kernel version, I can map it to the exact rootless limitation and provide a targeted fix (including which socket path and which sysctls/packages you need).