By default, the Docker daemon runs as root. That means every container you launch effectively sits one kernel exploit away from full control of your host. Rootless mode flips this: the daemon and all containers run as an ordinary unprivileged user, so even if a container breaks out, the attacker gets a powerless user account instead of root. This guide walks you through installing Docker in rootless mode on Ubuntu 24.04 and 22.04, step by step, from a clean system to your first verified container.

What is rootless mode?

In normal ("rootful") Docker, the dockerd daemon starts as root and containers are launched by a root process. In rootless mode, the daemon is started by a regular user with no sudo rights. It relies on user namespaces (to map a range of UIDs inside the container to your single UID outside), rootlesskit (to forward ports and create network namespaces), and slirp4netns (for unprivileged networking). You lose a few niche features (privileged containers, binding ports below 1024 without a tweak, ping in some setups), but you gain a drastically smaller attack surface, which is exactly what you want on shared or internet-facing servers.

Prerequisites

  • Ubuntu 22.04 or 24.04 (this guide targets 24.04, everything also works on 22.04).
  • A non-root user with sudo access (rootless mode must be installed as the user who will run containers, never as root).
  • A working internet connection for downloading packages.

Step 1: Install the prerequisites

Rootless Docker needs uidmap (provides newuidmap/newgidmap, which set up the UID/GID mappings) and dbus-user-session (so the per-user systemd instance can start services). Install them first:

sudo apt-get update
sudo apt-get install -y uidmap dbus-user-session

The apt-get update refreshes your package lists; the two packages are small and install in seconds.

Step 2: Verify your UID has subordinate UID/GID ranges

Rootless mode maps container UIDs to a block of "subordinate" UIDs owned by your user. Ubuntu's adduser usually creates these automatically. Check:

grep "^$(whoami):" /etc/subuid
grep "^$(whoami):" /etc/subgid

You should see output like yourname:100000:65536. If the lines are missing, create them manually (replace 100000 with an unused base):

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)

This reserves 65,536 UIDs and GIDs starting at 100000 for your account, which the rootless daemon maps into containers.

Step 3: Install Docker Engine from the official repository

Ubuntu's universe repository ships an older Docker. Install the current Docker CE from Docker's own apt repository so you get the rootless extras package:

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

This downloads Docker's GPG key and stores it in the keyrings directory so apt can verify Docker's packages.

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 docker-ce-rootless-extras

The first command registers Docker's repository for your Ubuntu codename (noble on 24.04, jammy on 22.04). The second installs the engine, CLI, Compose plugin, and crucially docker-ce-rootless-extras, which contains the rootless setup script, rootlesskit, and slirp4netns.

Step 4: Disable the rootful system daemon

You do not want the root daemon and the rootless daemon fighting over the same socket and port bindings. Disable and stop the system-wide service:

sudo systemctl disable --now docker.service docker.socket
sudo rm -f /var/run/docker.sock

disable --now stops the daemon immediately and prevents it from starting on boot. Removing the stale socket file avoids the CLI talking to a dead rootful socket later.

Step 5: Run the rootless setup script (as your user, no sudo)

Now switch to the account that will run containers and run the setup tool. Do not use sudo here:

dockerd-rootless-setuptool.sh install

The script installs a per-user systemd unit (~/.config/systemd/user/docker.service), creates symlinks in ~/bin, and prints the environment variables you need. If you ever want to remove it, dockerd-rootless-setuptool.sh uninstall reverses everything.

Step 6: Enable lingering so Docker survives logout

Per-user systemd services normally stop when you log out. Enable lingering so your rootless daemon keeps running:

sudo loginctl enable-linger $(whoami)

Then start the daemon and check it came up:

systemctl --user start docker
systemctl --user status docker --no-pager

The first command turns on lingering for your account; the second starts the user-level docker.service you just installed.

Step 7: Point your shell at the rootless socket

The Docker CLI talks to the daemon over a socket. Tell it where the rootless socket lives by adding two lines to your shell profile:

echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix:///run/user/$UID/docker.sock' >> ~/.bashrc
source ~/.bashrc

The PATH addition picks up the rootless docker wrapper in ~/bin; DOCKER_HOST points the CLI at your per-user socket (/run/user/$UID is your user's runtime directory).

Step 8: Verify rootless mode is working

Pull and run the classic test image, then confirm the daemon is rootless:

docker run --rm hello-world
docker info -f '{{.SecurityOptions}}' | grep -o rootless
docker info -f 'Rootless: {{.Rootless}}'

The hello-world container proves the daemon can pull and run images. The docker info checks confirm rootless appears in the security options, which means you are genuinely running without root.

Step 9 (optional): Allow binding ports below 1024

Unprivileged processes cannot bind ports under 1024 by default, so docker run -p 80:80 will fail out of the box. If you need web ports, relax the kernel restriction:

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

The first command applies the change immediately; the second persists it across reboots. Alternatively, just publish high ports (e.g. -p 8080:80) and put Nginx or a reverse proxy in front.

Verify it worked

  • docker run --rm hello-world prints the "Hello from Docker!" message.
  • docker info shows rootless under Security Options.
  • ps aux | grep [d]ockerd shows the daemon running as your username, not root.
  • docker ps works without sudo for your user.

Common issues and fixes

1. "Cannot connect to the Docker daemon" for your user

Cause: DOCKER_HOST is not set in the current shell, or the user daemon is not running.
Fix: confirm the variable and the service: echo $DOCKER_HOST should print unix:///run/user/$UID/docker.sock; start the daemon with systemctl --user start docker if needed. Then re-source your profile: source ~/.bashrc.

2. Port binding fails on ports below 1024

Cause: unprivileged users cannot bind privileged ports.
Fix: either publish a high port (-p 8080:80) or apply the ip_unprivileged_port_start=0 sysctl from Step 9.

3. ping inside containers does not work

Cause: slirp4netns cannot grant cap_net_raw to unprivileged containers, so ICMP is dropped.
Fix: this is expected in rootless mode. Test connectivity with curl or TCP-based checks instead of ping.

4. newuidmap: write to uid_map failed: Operation not permitted

Cause: missing or wrong /etc/subuid//etc/subgid entries, or uidmap not installed.
Fix: install uidmap (Step 1) and verify the subuid/subgid lines from Step 2. If you edited them, log out and back in so the change takes effect.

5. Containers stop when you log out of SSH

Cause: lingering is not enabled, so systemd kills user services on logout.
Fix: run sudo loginctl enable-linger $(whoami) and restart the daemon with systemctl --user restart docker.

Conclusion

You now have a fully working Docker installation that never touches root. The daemon, the CLI, and every container run as your ordinary user account, which removes the single biggest privilege-escalation path in a container host. For production servers, combine rootless Docker with a firewall, automatic security updates, and regular image scanning, and your container workloads will be meaningfully harder to compromise.

Setting up secure, production-ready Docker hosts is exactly what we do at XpertBees. If you would rather have our DevOps team harden your servers, set up CI/CD, or migrate workloads to containers, get in touch with us and we will handle it end to end.