| name | setup-docker |
| description | Use when the user wants to install Docker and optionally Sysbox on a VPS server. Triggered by /setup-docker or requests to install Docker on a server. |
/setup-docker -- Docker & Sysbox Installation
Install Docker CE with Compose plugin, harden the daemon for production use, and optionally install the Sysbox runtime for secure Docker-in-Docker. Prerequisite: SSH access to the server with a sudo user and app user already created (run /setup-vps first or equivalent).
Env Var Discovery
Before doing anything, find connection details. Follow this logic exactly:
- Read
.env in the project root. If it contains ANY of SSH_USER, SSH_PORT, VPS_IP, or SSH_KEY, use .env as ENV_FILE.
- Else read
.env.vps. If it exists and contains any of those vars, use .env.vps as ENV_FILE.
- If neither file has the vars, ask the user interactively for each value, then save them to
.env.vps. Also ensure .env.vps is listed in .gitignore (append if not present).
- Display all discovered values and ask the user to confirm before proceeding.
Variables and defaults
VPS_IP= # required, no default
SSH_USER=admin # sudo user (post-hardening default)
SSH_PORT=222 # SSH port (post-hardening default)
SSH_KEY=~/.ssh/id_ed25519 # SSH key path
VPS_SUDO_USER=admin # admin user (must exist on server)
VPS_APP_USER=appuser # unprivileged app user (must exist on server)
SSH Connection Pattern
For every remote command, use this pattern:
ssh -i $SSH_KEY -p $SSH_PORT -o StrictHostKeyChecking=accept-new $SSH_USER@$VPS_IP "<command>"
For multi-line scripts, use heredoc:
ssh -i $SSH_KEY -p $SSH_PORT -o StrictHostKeyChecking=accept-new $SSH_USER@$VPS_IP bash <<'REMOTE'
set -euo pipefail
REMOTE
Prerequisite Check
Before starting installation, verify the server is reachable and the required users exist.
echo "SSH OK"
id "$VPS_SUDO_USER" && id "$VPS_APP_USER"
If SSH fails, check the connection details in ENV_FILE. If either user does not exist, tell the user to run /setup-vps first to create them.
Section 1: Install Docker
Check if Docker is already installed. If so, report the version and skip to Section 2.
if command -v docker &>/dev/null && docker --version &>/dev/null; then
echo "Docker already installed: $(docker --version)"
echo "Compose: $(docker compose version 2>/dev/null || echo 'not found')"
echo "Skipping installation."
exit 0
fi
If Docker is not installed, proceed with installation. Detect the distro and adapt commands.
Debian/Ubuntu
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
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$(. /etc/os-release && echo "$ID") \
$(. /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-buildx-plugin docker-compose-plugin
For RHEL/Fedora: Use dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo then dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin.
Post-install (all distros)
sudo usermod -aG docker $VPS_APP_USER
sudo usermod -aG docker $VPS_SUDO_USER
sudo systemctl enable docker
sudo systemctl start docker
If apt install docker-ce fails with "Unable to locate package":
Check that the GPG key and repo entry exist:
ls /etc/apt/keyrings/docker.gpg && cat /etc/apt/sources.list.d/docker.list
If either is missing, re-run the GPG key and repository setup commands above. Also verify the distro codename is correct in the repo URL -- some distros (Linux Mint, Pop!_OS) need the upstream Ubuntu codename.
If GPG key import fails:
The gpg --dearmor command may fail if the keyring file already exists. Remove it first:
sudo rm -f /etc/apt/keyrings/docker.gpg
Then retry the GPG key import.
Section 2: Docker Daemon Hardening
Write the hardened daemon configuration. If /etc/docker/daemon.json already exists, back it up first.
sudo mkdir -p /etc/docker
if [ -f /etc/docker/daemon.json ]; then
sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.backup
echo "Existing daemon.json backed up to daemon.json.backup"
fi
sudo tee /etc/docker/daemon.json << 'EOF'
{
"ip": "127.0.0.1",
"default-network-opts": {
"bridge": {
"com.docker.network.bridge.host_binding_ipv4": "127.0.0.1"
}
},
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
},
"storage-driver": "overlay2",
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
}
}
}
EOF
sudo systemctl restart docker
If Docker fails to restart after daemon.json changes:
Validate the JSON syntax:
sudo cat /etc/docker/daemon.json | python3 -m json.tool
Fix any syntax errors and retry. Also check the journal for specific error messages:
sudo journalctl -u docker --no-pager -n 20
Common issues: duplicate keys, trailing commas, incompatible options with the installed Docker version.
Section 3: Install Sysbox (optional)
STOP. Ask the user: "Do you need Docker-in-Docker support (Sysbox)? This is needed for running Docker inside containers (e.g., OpenClaw sandboxes). If you're just running regular containers, you can skip this."
If the user says no, skip this entire section and go to Section 4.
If yes, proceed below.
3a: Check if already installed
if command -v sysbox-runc &>/dev/null; then
echo "Sysbox already installed: $(sysbox-runc --version)"
echo "Skipping installation."
exit 0
fi
3b: Download and install
SYSBOX_VERSION="0.6.7"
SYSBOX_SHA256="b7ac389e5a19592cadf16e0ca30e40919516128f6e1b7f99e1cb4ff64554172e"
SYSBOX_DEB="sysbox-ce_${SYSBOX_VERSION}-0.linux_amd64.deb"
wget "https://downloads.nestybox.com/sysbox/releases/v${SYSBOX_VERSION}/${SYSBOX_DEB}"
echo "${SYSBOX_SHA256} ${SYSBOX_DEB}" | sha256sum -c -
sudo apt install -y jq fuse
sudo dpkg -i "${SYSBOX_DEB}"
rm "${SYSBOX_DEB}"
If sha256sum fails: The download may be corrupted. Delete the file and re-download:
rm "${SYSBOX_DEB}"
wget "https://downloads.nestybox.com/sysbox/releases/v${SYSBOX_VERSION}/${SYSBOX_DEB}"
If dpkg -i fails with dependency errors:
sudo apt --fix-broken install -y
3c: AppArmor fusermount3 fix
Ubuntu 25.04+ ships a fusermount3 AppArmor profile in enforce mode that blocks sysbox-fs from creating FUSE mounts. Check if it applies and fix:
if sudo aa-status 2>/dev/null | grep -q 'fusermount3'; then
echo "fusermount3 AppArmor profile is enforcing -- disabling for sysbox-fs compatibility"
sudo ln -sf /etc/apparmor.d/fusermount3 /etc/apparmor.d/disable/
sudo apparmor_parser -R /etc/apparmor.d/fusermount3 2>/dev/null || true
sudo systemctl restart sysbox-fs sysbox-mgr sysbox
echo "fusermount3 profile disabled, sysbox restarted"
else
echo "fusermount3 AppArmor profile not enforcing -- no action needed"
fi
This is not needed on Ubuntu 24.04 where the profile is absent or in complain mode. On a hardened VPS with key-only SSH, disabling this profile has negligible security impact.
3d: Overlayfs ID-mapped mount workaround
Sysbox 0.6.7 detects kernel support for ID-mapped mounts on overlayfs but fails to apply them, causing files inside containers to appear as nobody:nogroup. Apply a systemd override to force rootfs cloning instead:
sudo mkdir -p /etc/systemd/system/sysbox-mgr.service.d
sudo tee /etc/systemd/system/sysbox-mgr.service.d/override.conf << 'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/sysbox-mgr --disable-ovfs-on-idmapped-mount --log-level info
EOF
sudo systemctl daemon-reload
sudo systemctl restart sysbox
When to remove: Once a future Sysbox release fixes this bug, remove the override:
sudo rm /etc/systemd/system/sysbox-mgr.service.d/override.conf
sudo systemctl daemon-reload && sudo systemctl restart sysbox
Section 4: Verification
Run all checks and report results to the user.
docker --version
docker compose version
docker info | grep -E "Storage Driver|Logging Driver|Default Runtime"
docker info | grep -A2 "Default Address Pools"
sudo -u $VPS_APP_USER docker ps
cat /etc/docker/daemon.json | python3 -m json.tool
If Sysbox was installed, also verify:
docker info | grep -i "sysbox"
sudo systemctl is-active sysbox
sudo systemctl show sysbox-mgr --property=ExecStart | grep -q 'disable-ovfs-on-idmapped-mount' \
&& echo "sysbox-mgr override: ACTIVE" \
|| echo "sysbox-mgr override: NOT APPLIED"
Print a summary with pass/fail for each check:
- Docker installed and running
- Docker Compose available
- Daemon hardening applied (127.0.0.1 binding, log rotation, overlay2, no-new-privileges)
- App user can run docker commands
- Sysbox runtime registered (if installed)
- Sysbox overlayfs workaround active (if installed)
If any check fails, report the failure and suggest remediation.