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 does not need it) ────────────────
sudo systemctl disable --now docker.service docker.socket 2>/dev/null || true
# ─── 5. Detect whether a systemd user session is usable ───────────────────────
# systemctl --user show-environment exits non-zero when there is no
# systemd, no live user manager, or no systemd-logind session. The
# result decides which socket path the rest of the script targets.
if systemctl --user show-environment >/dev/null 2>&1; then
USE_SYSTEMD=1
SOCKET_PATH="/run/user/$(id -u)/docker.sock"
else
USE_SYSTEMD=0
export XDG_RUNTIME_DIR="$HOME/.docker/run"
mkdir -p "$XDG_RUNTIME_DIR"
SOCKET_PATH="$XDG_RUNTIME_DIR/docker.sock"
fi
export DOCKER_HOST="unix://$SOCKET_PATH"
export PATH="/usr/bin:$PATH"
# ─── 6. Install rootless Docker ───────────────────────────────────────────────
dockerd-rootless-setuptool.sh install
# ─── 7. Start dockerd ─────────────────────────────────────────────────────────
if [ "$USE_SYSTEMD" -eq 1 ]; then
systemctl --user enable --now docker
sudo loginctl enable-linger "$(whoami)"
echo "[INFO] Started via systemd user session."
else
echo "[INFO] No systemd user session, starting dockerd-rootless.sh manually."
dockerd-rootless.sh > /tmp/dockerd-rootless.log 2>&1 &
disown
CRON_JOB="@reboot XDG_RUNTIME_DIR=$HOME/.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
fi
# ─── 8. Persist env vars to ~/.bashrc (safe on re-run) ────────────────────────
# Back up ~/.bashrc once, strip any prior entries we may have written,
# then append the canonical values for the detected path.
if [ ! -f ~/.bashrc.reoclo-bak ]; then
cp ~/.bashrc ~/.bashrc.reoclo-bak
fi
sed -i \
-e '/^export XDG_RUNTIME_DIR=.*\.docker\/run/d' \
-e '/^export DOCKER_HOST=unix:\/\//d' \
-e '/^export PATH=\/usr\/bin:\$PATH/d' \
~/.bashrc
if [ "$USE_SYSTEMD" -eq 0 ]; then
echo 'export XDG_RUNTIME_DIR="$HOME/.docker/run"' >> ~/.bashrc
fi
echo "export DOCKER_HOST=$DOCKER_HOST" >> ~/.bashrc
echo 'export PATH=/usr/bin:$PATH' >> ~/.bashrc
# ─── 9. Kernel tuning (allow rootless containers to bind 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 ""
if docker info 2>&1 | grep -qi rootless; then
echo "Rootless confirmed"
else
echo "Rootless flag not found. Check /tmp/dockerd-rootless.log for clues."
exit 1
fi
docker run --rm hello-world
echo ""
echo "Rootless Docker is ready. Now install the Reoclo Runner."