Run OpenClaw in a Rootless Podman Container
This page explains how to run the OpenClaw Gateway inside a rootless Podman container using your existing user. It covers prerequisites, setup, and management commands for developers and system administrators.
Read this when
- You want a containerized gateway with Podman instead of Docker
Run the OpenClaw Gateway inside a rootless Podman container, with your existing non-root user handling management.
How the pieces fit together:
- Podman is responsible for running the gateway container.
- Your host-side
openclawCLI acts as the control plane. - Persistent data is stored on the host, defaulting to
~/.openclaw. - Routine administration relies on
openclaw --container <name> ...rather thansudo -u openclaw,podman exec, or a dedicated service account.
Prerequisites
- Podman operating in rootless mode
- OpenClaw CLI present on the host
- Optional:
systemd --userwhen you want Quadlet-driven auto-start - Optional:
sudosolely if you needloginctl enable-linger "$(whoami)"for boot-time persistence on a headless machine
Quick start
One-time setup
Execute ./scripts/podman/setup.sh from the repository root.
That command compiles openclaw:local into your rootless Podman storage (or fetches OPENCLAW_IMAGE / OPENCLAW_PODMAN_IMAGE when those are configured), establishes ~/.openclaw/openclaw.json with gateway.mode: "local" if it does not exist, and sets up ~/.openclaw/.env with a freshly generated OPENCLAW_GATEWAY_TOKEN when needed.
Environment variables you can set at build time:
| Var | Effect |
|---|---|
OPENCLAW_IMAGE / OPENCLAW_PODMAN_IMAGE | Leverage an existing or pulled image instead of compiling openclaw:local |
OPENCLAW_IMAGE_APT_PACKAGES | Add extra apt packages during the image build (the legacy OPENCLAW_DOCKER_APT_PACKAGES form is also accepted) |
OPENCLAW_IMAGE_PIP_PACKAGES | Add extra Python packages during the image build; pin versions and rely only on package indexes you trust |
OPENCLAW_EXTENSIONS | Compile and package selected supported plugins, then install their runtime dependencies |
OPENCLAW_INSTALL_BROWSER | Pre-load Chromium and Xvfb for browser automation (set to 1) |
For a Quadlet-managed setup instead (Linux plus systemd user services only):
./scripts/podman/setup.sh --quadlet
Alternatively, set OPENCLAW_PODMAN_QUADLET=1.
Start the Gateway container
./scripts/run-openclaw-podman.sh launch
The container launches with your current uid/gid via --userns=keep-id, and your OpenClaw state is bind-mounted into it.
Run onboarding inside the container
./scripts/run-openclaw-podman.sh launch setup
After that, navigate to http://127.0.0.1:18789/ and grab the token found in ~/.openclaw/.env.
Model auth: during setup, rely on OpenClaw-managed authentication (Anthropic API keys, or OpenAI Codex browser OAuth/device-code auth for Codex-backed OpenAI). The Podman launcher does not expose host CLI credential homes like ~/.claude or ~/.codex to either the setup or gateway container. Host CLI logins that already exist are just same-host conveniences; for container installs, keep provider auth inside the mounted ~/.openclaw state that setup manages.
Manage the running container from the host CLI
export OPENCLAW_CONTAINER=openclaw
From there, standard openclaw commands execute inside that container without extra steps:
openclaw dashboard --no-open
openclaw gateway status --deep # includes extra service scan
openclaw doctor
openclaw channels login
On macOS, the Podman machine can make the browser look non-local to the gateway. If the Control UI shows device-auth errors after startup, follow the Tailscale guidance in Podman and Tailscale.
The manual launcher reads only a narrow allowlist of Podman-related keys from ~/.openclaw/.env and passes explicit runtime env vars to the container; the full env file is never handed to Podman.
Agent sandbox backend
This page is about running the Gateway itself in a Podman container. Agent sandboxing is a separate concern. Pick agents.defaults.sandbox.backend: "podman" to use the native Podman CLI directly. The default "docker" backend still supports Docker only.
Podman applies the same agents.defaults.sandbox.docker.* container settings as Docker but runs them through the native podman CLI. Browser sandboxes remain Docker-only for now.
Check Sandboxing for the config example and the image-build command.
Podman and Tailscale
For HTTPS or remote browser access, refer to the main Tailscale documentation.
Notes specific to Podman:
- Keep the Podman publish host at
127.0.0.1. - Favor host-managed
tailscale serveoveropenclaw gateway --tailscale serve. - On macOS, if local browser device-auth context is unreliable, go with Tailscale access rather than ad hoc local tunnel workarounds.
See Tailscale and Control UI.
Systemd (Quadlet, optional)
Running ./scripts/podman/setup.sh --quadlet causes setup to place a Quadlet file at ~/.config/containers/systemd/openclaw.container.
| Action | Command |
|---|---|
| Start | systemctl --user start openclaw.service |
| Stop | systemctl --user stop openclaw.service |
| Status | systemctl --user status openclaw.service |
| Logs | journalctl --user -u openclaw.service -f |
Once the Quadlet file has been modified:
systemctl --user daemon-reload
systemctl --user restart openclaw.service
To make the service persist across reboots on SSH/headless hosts, turn on lingering for the current user:
sudo loginctl enable-linger "$(whoami)"
The Quadlet service that gets generated ships with a locked-down, predictable configuration: 127.0.0.1 published ports (18789 gateway, 18790 bridge), --bind lan inside the container, keep-id user namespace, OPENCLAW_NO_RESPAWN=1, Restart=on-failure, and TimeoutStartSec=300. It pulls ~/.openclaw/.env as a runtime EnvironmentFile for settings such as OPENCLAW_GATEWAY_TOKEN, yet it ignores the manual launcher's Podman-specific override allowlist. When you need custom publish ports, a different publish host, or other container-run options, go with the manual launcher instead, or change ~/.config/containers/systemd/openclaw.container directly and then reload and restart the service.
Config, env, and storage
- Config dir:
~/.openclaw - Workspace dir:
~/.openclaw/workspace - Token file:
~/.openclaw/.env - Launch helper:
./scripts/run-openclaw-podman.sh
Host state is bind-mounted into the container by the launch script and Quadlet: OPENCLAW_CONFIG_DIR -> /home/node/.openclaw, OPENCLAW_WORKSPACE_DIR -> /home/node/.openclaw/workspace. By default these point to host directories rather than anonymous container state, which means openclaw.json, per-agent auth-profiles.json, channel/provider state, sessions, and workspace all survive container replacement. Setup also populates gateway.controlUi.allowedOrigins for 127.0.0.1 and localhost on the published gateway port, ensuring the local dashboard works with the container's non-loopback bind.
Environment variables that are handy for the manual launcher (save these in ~/.openclaw/.env; the launcher reads that file before it finalizes container/image defaults):
| Var | Default | Effect |
|---|---|---|
OPENCLAW_PODMAN_CONTAINER | openclaw | Container name |
OPENCLAW_PODMAN_IMAGE / OPENCLAW_IMAGE | openclaw:local | Image to run |
OPENCLAW_PODMAN_GATEWAY_HOST_PORT | 18789 | Host port mapped to container 18789 |
OPENCLAW_PODMAN_BRIDGE_HOST_PORT | 18790 | Host port mapped to container 18790 |
OPENCLAW_PODMAN_PUBLISH_HOST | 127.0.0.1 | Host interface for published ports |
OPENCLAW_GATEWAY_BIND | lan | Gateway bind mode inside the container |
OPENCLAW_PODMAN_USERNS | keep-id | keep-id, auto, or host |
When OPENCLAW_CONFIG_DIR or OPENCLAW_WORKSPACE_DIR deviates from the default, apply the same variables to both ./scripts/podman/setup.sh and subsequent ./scripts/run-openclaw-podman.sh launch commands, since the repo-local launcher does not carry custom path overrides across shells.
Upgrading images
Once a fresh image has been pulled or rebuilt, bring the container, or the Quadlet service, back up. When a new OpenClaw version boots for the first time, the gateway performs safe state and plugin repairs before it signals readiness.
Should the gateway terminate rather than reach a ready state, execute that same image once with openclaw doctor --fix while pointing at the identical mounted state and config, and then relaunch the gateway normally:
OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}"
OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$OPENCLAW_CONFIG_DIR/workspace}"
OPENCLAW_PODMAN_IMAGE="${OPENCLAW_PODMAN_IMAGE:-${OPENCLAW_IMAGE:-openclaw:local}}"
podman run --rm -it \
--userns=keep-id \
--user "$(id -u):$(id -g)" \
-e HOME=/home/node \
-e NPM_CONFIG_CACHE=/home/node/.openclaw/.npm \
-v "$OPENCLAW_CONFIG_DIR:/home/node/.openclaw:rw" \
-v "$OPENCLAW_WORKSPACE_DIR:/home/node/.openclaw/workspace:rw" \
"$OPENCLAW_PODMAN_IMAGE" \
openclaw doctor --fix
On hosts where SELinux is active, append ,Z to both bind mounts if Podman denies access to the mounted state.
Once the Gateway has been restarted with the updated image, invoke the read-only deployment preflight through the container-aware host CLI:
export OPENCLAW_CONTAINER=openclaw
openclaw doctor --json
Useful commands
- Container logs:
podman logs -f openclaw - Stop container:
podman stop openclaw - Remove container:
podman rm -f openclaw - Open dashboard URL from host CLI:
openclaw dashboard --no-open - Health/status via host CLI:
openclaw gateway status --deep(RPC probe + extra service scan)
Troubleshooting
- Permission denied (EACCES) on config or workspace: By default, the container operates with
--userns=keep-idand--user <your uid>:<your gid>. Verify that your current user owns the host config and workspace paths. - Gateway start blocked (missing
gateway.mode=local): Confirm that~/.openclaw/openclaw.jsonis present and definesgateway.mode="local". If it is absent,scripts/podman/setup.shwill generate it. - Container restarts after an image update: Execute the one-off
openclaw doctor --fixcommand from Upgrading images, and then relaunch the gateway. - Container CLI commands hit the wrong target: Pass
openclaw --container <name> ...explicitly, or setOPENCLAW_CONTAINER=<name>in your shell environment. openclaw updatefails with--container: This is normal. Rebuild or pull the image, then restart the container or the Quadlet service.- Quadlet service does not start: Run
systemctl --user daemon-reloadfollowed bysystemctl --user start openclaw.service. On systems without a display,sudo loginctl enable-linger "$(whoami)"might also be required. - SELinux blocks bind mounts: Keep the default mount behavior unchanged; the launcher automatically adds
:Zon Linux when SELinux is enforcing or permissive.