Skip to content

Rootless Docker Setup

By default, Docker requires root privileges. Rootless mode runs the Docker daemon and containers as a regular user, eliminating the need for sudo or adding users to the docker group. This is the most secure way to run Docker with the Reoclo Runner.

With standard Docker, anyone in the docker group effectively has root access to the host. Rootless Docker eliminates this risk:

  • The Docker daemon runs as your user, not root
  • Containers cannot escalate to root on the host
  • No docker group membership needed
  • Better isolation between users on shared servers
  • Linux with a 64-bit kernel (Ubuntu 20.04+, Debian 11+, Fedora 36+, CentOS 9+, or similar)
  • systemd with user session support (recommended). Systems without a user systemd (minimal container images, some VPS providers) are supported via the fallback documented in the Complete Setup Scriptdockerd-rootless.sh is started manually and restarted on boot via a @reboot crontab entry.
  • A non-root user that will run both Docker and the Reoclo Runner

Install the uidmap package which provides newuidmap and newgidmap:

Ubuntu / Debian:

Terminal window
sudo apt update
sudo apt install -y uidmap dbus-user-session

Fedora / CentOS / RHEL:

Terminal window
sudo dnf install -y shadow-utils fuse-overlayfs

Your user needs at least 65,536 subordinate UIDs and GIDs. Check if they’re already configured:

Terminal window
grep ^$(whoami): /etc/subuid
grep ^$(whoami): /etc/subgid

If these return empty, add the mappings:

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

Verify the configuration:

Terminal window
grep ^$(whoami): /etc/subuid
# Expected output: yourusername:100000:65536

Step 3: Disable System Docker (If Installed)

Section titled “Step 3: Disable System Docker (If Installed)”

If you have a system-wide Docker installation, disable it first to avoid conflicts:

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

If Docker is not installed at all, skip this step.

If Docker is not installed yet, install it first:

Ubuntu / Debian:

Terminal window
# Add Docker's official GPG key and repository
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] 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 update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-ce-rootless-extras

Fedora:

Terminal window
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-ce-rootless-extras

The docker-ce-rootless-extras package is required for rootless mode.

Run the rootless setup script as your non-root user (not with sudo):

Terminal window
dockerd-rootless-setuptool.sh install

The script will output environment variables to add to your shell. Add them to ~/.bashrc:

Terminal window
echo 'export PATH=/usr/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' >> ~/.bashrc
source ~/.bashrc

Start rootless Docker and enable it on boot:

Terminal window
systemctl --user start docker
systemctl --user enable docker

Enable lingering so the service runs even when you’re not logged in:

Terminal window
sudo loginctl enable-linger $(whoami)
Terminal window
docker info 2>&1 | grep -i "root"

Expected output:

rootless: true

Run a quick test:

Terminal window
docker run --rm hello-world

If you see “Hello from Docker!”, rootless Docker is working.

Also verify the socket path:

Terminal window
echo $DOCKER_HOST
# Expected: unix:///run/user/<your-uid>/docker.sock

Now install the Reoclo Runner as the same user. The runner will automatically use rootless Docker since DOCKER_HOST is set in your environment.

  1. Go to Servers > Add Server in the Reoclo dashboard
  2. Click Generate Registration Token
  3. Run the install command on your server:
Terminal window
curl -fsSL https://get.reoclo.com/install.sh | bash -s -- <your-token>

The runner’s systemd service runs as your user and inherits the DOCKER_HOST environment variable, so it talks to rootless Docker automatically.

By default, rootless Docker cannot bind to ports below 1024 (like 80 and 443). To allow this:

Option A: Set the unprivileged port start (recommended):

Terminal window
sudo sh -c 'echo 0 > /proc/sys/net/ipv4/ip_unprivileged_port_start'

Make it permanent:

Terminal window
sudo sh -c 'echo "net.ipv4.ip_unprivileged_port_start=0" >> /etc/sysctl.d/99-rootless-docker.conf'
sudo sysctl --system

Option B: Use a reverse proxy on the host:

Run a host-level reverse proxy (like Caddy or nginx) that listens on ports 80/443 and forwards to Docker’s high ports. This is what Reoclo’s managed proxy does automatically.

Rootless Docker stores images and containers in your home directory by default:

~/.local/share/docker/

Ensure your home partition has sufficient disk space for Docker images and build caches. For production servers, consider mounting a dedicated volume at this path.

Check current usage:

Terminal window
docker system df

Most web application deployments work identically in rootless mode. The main limitations are:

LimitationImpactWorkaround
Ports below 1024Cannot bind by defaultSet ip_unprivileged_port_start=0 or use a reverse proxy
--net=host networkingLimited functionalityUse default bridge networking (recommended anyway)
AppArmor / SELinuxSome profiles may need adjustmentUsually works out of the box on Ubuntu/Fedora
cgroup v1 systemsLimited resource managementUpgrade to cgroup v2 (default on modern distros)

None of these affect typical Reoclo deployments, which use bridge networking and high ports with a managed reverse proxy.

Ensure the Docker binaries are in your PATH:

Terminal window
echo 'export PATH=/usr/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

Verify the DOCKER_HOST variable and service status:

Terminal window
echo $DOCKER_HOST
systemctl --user status docker

If the service isn’t running, start it:

Terminal window
systemctl --user start docker

Your user doesn’t have subordinate UID/GID mappings. Run Step 2 above to configure them.

Enable lingering:

Terminal window
sudo loginctl enable-linger $(whoami)

Ensure the runner was installed as the same user that set up rootless Docker. Check that the runner’s systemd service has the correct DOCKER_HOST:

Terminal window
systemctl --user show reoclo-runner | grep Environment

If DOCKER_HOST is not set, add it to the service override:

Terminal window
mkdir -p ~/.config/systemd/user/reoclo-runner.service.d
cat > ~/.config/systemd/user/reoclo-runner.service.d/docker.conf << 'EOF'
[Service]
Environment="DOCKER_HOST=unix:///run/user/%U/docker.sock"
EOF
systemctl --user daemon-reload
systemctl --user restart reoclo-runner

For convenience, here’s the full setup in one block (Ubuntu/Debian). Run as your non-root user. The script handles both the standard systemd case and the fallback for systems without a systemd user session (e.g., minimal Ubuntu/Debian images, LXC containers, some VPS providers): it detects whether systemctl --user works and, if not, starts dockerd-rootless.sh manually and installs a @reboot crontab entry for persistence.

#!/bin/bash
set -e
# ─── 1. System packages ───────────────────────────────────────────────────────
sudo apt update
sudo apt install -y uidmap dbus-user-session ca-certificates curl
# ─── 2. Sub-UID/GID mapping ───────────────────────────────────────────────────
grep -q "^$(whoami):" /etc/subuid || sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)
# ─── 3. Docker apt repo ───────────────────────────────────────────────────────
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] 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 update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-ce-rootless-extras
# ─── 4. Disable system-wide Docker (rootless doesn't need it) ─────────────────
sudo systemctl disable --now docker.service docker.socket 2>/dev/null || true
# ─── 5. Set up XDG_RUNTIME_DIR before running the installer ──────────────────
# (required when systemd is not available — avoids /run/user/UID which
# only exists when systemd-logind manages the user session)
export XDG_RUNTIME_DIR=/home/$(whoami)/.docker/run
mkdir -p "$XDG_RUNTIME_DIR"
# ─── 6. Install rootless Docker ───────────────────────────────────────────────
dockerd-rootless-setuptool.sh install
# ─── 7. Persist env vars to .bashrc ──────────────────────────────────────────
# Use a socket path under the user's home so it works with or without
# systemd — /run/user/UID is only available when systemd-logind runs.
grep -qxF 'export XDG_RUNTIME_DIR=/home/'"$(whoami)"'/.docker/run' ~/.bashrc || \
echo 'export XDG_RUNTIME_DIR=/home/'"$(whoami)"'/.docker/run' >> ~/.bashrc
grep -qxF 'export DOCKER_HOST=unix:///home/'"$(whoami)"'/.docker/run/docker.sock' ~/.bashrc || \
echo 'export DOCKER_HOST=unix:///home/'"$(whoami)"'/.docker/run/docker.sock' >> ~/.bashrc
grep -qxF 'export PATH=/usr/bin:$PATH' ~/.bashrc || \
echo 'export PATH=/usr/bin:$PATH' >> ~/.bashrc
export DOCKER_HOST=unix:///home/$(whoami)/.docker/run/docker.sock
export PATH=/usr/bin:$PATH
# ─── 8. Start dockerd (systemd-aware, falls back to manual) ──────────────────
if systemctl --user start docker 2>/dev/null; then
systemctl --user enable docker
sudo loginctl enable-linger $(whoami) 2>/dev/null || true
echo "[INFO] Started via systemd user session."
else
echo "[INFO] systemd not available — starting dockerd-rootless.sh manually."
mkdir -p "$XDG_RUNTIME_DIR"
dockerd-rootless.sh > /tmp/dockerd-rootless.log 2>&1 &
disown
# Add crontab entry for reboot persistence (skip if already present)
CRON_JOB="@reboot XDG_RUNTIME_DIR=/home/$(whoami)/.docker/run PATH=/usr/bin:/sbin:/usr/sbin:/bin dockerd-rootless.sh >> /tmp/dockerd-rootless.log 2>&1"
( crontab -l 2>/dev/null | grep -qxF "$CRON_JOB" ) || \
( crontab -l 2>/dev/null; echo "$CRON_JOB" ) | crontab -
echo "[INFO] Crontab entry added for reboot persistence."
sleep 3 # give the daemon a moment to start
fi
# ─── 9. Kernel tuning (allow low ports) ──────────────────────────────────────
sudo sh -c 'echo "net.ipv4.ip_unprivileged_port_start=0" >> /etc/sysctl.d/99-rootless-docker.conf'
sudo sysctl --system > /dev/null
# ─── 10. Verify ───────────────────────────────────────────────────────────────
echo ""
docker info 2>&1 | grep -i rootless && echo "✓ Rootless confirmed" || echo "✗ Rootless flag not found — check /tmp/dockerd-rootless.log"
docker run --rm hello-world
echo ""
echo "✅ Rootless Docker is ready. Now install the Reoclo Runner."