On this page

On this page

Containers

Podman

Run the OpenClaw Gateway in a rootless Podman container, managed by your current non-root user.

The model:

  • Podman runs the gateway container.
  • Your host openclaw CLI is the control plane.
  • Persistent state lives on the host under ~/.openclaw by default.
  • Day-to-day management uses openclaw --container <name> ... instead of sudo -u openclaw, podman exec, or a separate service user.

Prerequisites

  • Podman in rootless mode, with its host init executable (normally catatonit) available for the launcher's --init flag. On minimal Debian/Ubuntu hosts, install both explicitly: sudo apt-get install podman catatonit. For Podman Machine, the helper must be available inside the machine.
  • OpenClaw CLI installed on the host
  • Optional: systemd --user if you want Quadlet-managed auto-start
  • Optional: sudo only if you want loginctl enable-linger "$(whoami)" for boot persistence on a headless host

Quick start

  • One-time setup

    From the repo root, run ./scripts/podman/setup.sh.

    This builds openclaw:local in your rootless Podman store (or pulls OPENCLAW_IMAGE / OPENCLAW_PODMAN_IMAGE if set), creates ~/.openclaw/openclaw.json with gateway.mode: "local" if missing, and creates ~/.openclaw/.env with a generated OPENCLAW_GATEWAY_TOKEN if missing.

    Optional build-time env vars:

    Var Effect
    OPENCLAW_IMAGE / OPENCLAW_PODMAN_IMAGE Use an existing/pulled image instead of building openclaw:local
    OPENCLAW_IMAGE_APT_PACKAGES Install extra apt packages during image build (also accepts legacy OPENCLAW_DOCKER_APT_PACKAGES)
    OPENCLAW_IMAGE_PIP_PACKAGES Install extra Python packages during image build; pin versions and use only package indexes you trust
    OPENCLAW_EXTENSIONS Compile/package supported selected plugins and install their runtime dependencies
    OPENCLAW_INSTALL_BROWSER Pre-install Chromium and Xvfb for browser automation (set to 1)

    For Quadlet-managed setup instead (Linux + systemd user services only):

    bash
    ./scripts/podman/setup.sh --quadlet

    Or set OPENCLAW_PODMAN_QUADLET=1.

  • Start the Gateway container

    bash
    ./scripts/run-openclaw-podman.sh launch

    Starts the container as your current uid/gid with --userns=keep-id and bind-mounts your OpenClaw state into the container.

  • Run onboarding inside the container

    bash
    ./scripts/run-openclaw-podman.sh launch setup

    Then open http://127.0.0.1:18789/ and use the token from ~/.openclaw/.env.

    Model auth: use OpenClaw-managed auth during setup (Anthropic API keys, or OpenAI Codex browser OAuth/device-code auth for Codex-backed OpenAI). The Podman launcher does not mount host CLI credential homes such as ~/.claude or ~/.codex into the setup or gateway container. Existing host CLI logins are same-host convenience paths only -- for container installs, keep provider auth in the mounted ~/.openclaw state that setup manages.

  • Manage the running container from the host CLI

    bash
    export OPENCLAW_CONTAINER=openclaw

    Then normal openclaw commands run inside that container automatically:

    bash
    openclaw dashboard --no-openopenclaw gateway status --deep   # includes extra service scanopenclaw doctoropenclaw channels login

    On macOS, Podman machine may make the browser appear non-local to the gateway. If the Control UI reports device-auth errors after launch, use the Tailscale guidance in Podman and Tailscale.

  • The manual launcher reads only a small allowlist of Podman-related keys from ~/.openclaw/.env and passes explicit runtime env vars to the container; it does not hand the full env file to Podman.

    Agent sandbox backend

    This page covers running the Gateway itself in a Podman container. Agent sandboxing is separate. Set agents.defaults.sandbox.backend: "podman" to select the native Podman CLI directly. The default "docker" backend remains Docker-only.

    Podman reuses the same agents.defaults.sandbox.docker.* container settings as Docker but executes them through the native podman CLI. Browser sandboxes remain Docker-only for now.

    See Sandboxing for the config example and image-build command.

    Podman and Tailscale

    For HTTPS or remote browser access, follow the main Tailscale docs.

    Podman-specific notes:

    • Keep the Podman publish host at 127.0.0.1.
    • Prefer host-managed tailscale serve over openclaw gateway --tailscale serve.
    • On macOS, if local browser device-auth context is unreliable, use Tailscale access instead of ad hoc local tunnel workarounds.

    See Tailscale and Control UI.

    Reaching model servers on the host

    Inside the container, 127.0.0.1 is the container itself. To use a model server running on the host (llama.cpp, LM Studio, Ollama, vLLM, or any OpenAI-compatible endpoint), map the host-gateway alias:

    bash
    --add-host=host.docker.internal:host-gateway   # manual podman runAddHost=host.docker.internal:host-gateway      # Quadlet

    Recent Podman versions also provide host.containers.internal automatically. Reference the host server as http://host.docker.internal:<port>/v1 (or http://host.containers.internal:<port>/v1) in the provider baseUrl.

    See Local models for provider configuration and Docker networking, providers, and storage for the Docker equivalent.

    Systemd (Quadlet, optional)

    If you ran ./scripts/podman/setup.sh --quadlet, setup installs 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

    After editing the Quadlet file:

    bash
    systemctl --user daemon-reloadsystemctl --user restart openclaw.service

    For boot persistence on SSH/headless hosts, enable lingering for your current user:

    bash
    sudo loginctl enable-linger "$(whoami)"

    The generated Quadlet service keeps a fixed, hardened default shape: 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 reads ~/.openclaw/.env as a runtime EnvironmentFile for values such as OPENCLAW_GATEWAY_TOKEN, but does not consume the manual launcher's Podman-specific override allowlist. For custom publish ports, publish host, or other container-run flags, use the manual launcher instead, or edit ~/.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

    The launch script and Quadlet bind-mount host state into the container: OPENCLAW_CONFIG_DIR -> /home/node/.openclaw, OPENCLAW_WORKSPACE_DIR -> /home/node/.openclaw/workspace. By default those are host directories, not anonymous container state, so openclaw.json, shared and per-agent SQLite auth stores, channel/provider state, sessions, and workspace survive container replacement. Setup also seeds gateway.controlUi.allowedOrigins for 127.0.0.1 and localhost on the published gateway port so the local dashboard works with the container's non-loopback bind.

    Useful env vars for the manual launcher (persist these in ~/.openclaw/.env; the launcher reads that file before finalizing 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

    If you use a non-default OPENCLAW_CONFIG_DIR or OPENCLAW_WORKSPACE_DIR, set the same variables for both ./scripts/podman/setup.sh and later ./scripts/run-openclaw-podman.sh launch commands -- the repo-local launcher does not persist custom path overrides across shells.

    Upgrading images

    After you rebuild or pull a new image, restart the container or Quadlet service. On first startup for a new OpenClaw version, the gateway runs safe state and plugin repairs before reporting ready.

    If the gateway exits instead of becoming ready, run the same image once with openclaw doctor --fix against the same mounted state/config, then restart the gateway normally:

    bash
    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 SELinux hosts, add ,Z to both bind mounts if Podman blocks access to the mounted state.

    After restarting the Gateway with the updated image, run the read-only deployment preflight through the container-aware host CLI:

    bash
    export OPENCLAW_CONTAINER=openclawopenclaw 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 check + extra service scan)

    Troubleshooting

    • Token generation fails: Setup and launch stop before saving a generated token or starting the container when the selected random source (openssl, Python, or od) fails. Repair that command and retry.
    • Init executable missing (lookup init binary / container-init binary not found on the host): Install catatonit on the Podman engine host or repair its configured init_path/helper_binaries_dir in containers.conf, then retry. Installing the helper inside the Gateway or sandbox image does not repair the engine host. Keep --init enabled; see Host init prerequisite.
    • Permission denied (EACCES) on config or workspace: The container runs with --userns=keep-id and --user <your uid>:<your gid> by default. Check that the host config/workspace paths are owned by your current user. One-off podman run commands that touch the same state need the same mapping (--userns=keep-id) — without it the container uid doesn't match files the gateway created, even when the host paths are owned by you.
    • Gateway start blocked (missing gateway.mode=local): Check that ~/.openclaw/openclaw.json exists and sets gateway.mode="local". scripts/podman/setup.sh creates this if missing.
    • Published port unreachable but startup logs report ready: The gateway bound to loopback only. gateway.bind: "tailnet" and "loopback" are host-centric (see the Quadlet note above); inside a container there is no Tailscale interface, and tailnet falls back to loopback-only. Use the Quadlet default (--bind lan) or gateway.bind: "custom" + gateway.customBindHost: "0.0.0.0", and control exposure with published ports.
    • Config changes rejected with cannot verify Gateway ownership from this process: The running gateway locks openclaw.json and only admits writes from its own container/PID namespace. Run config-mutating commands through the container-aware host CLI (openclaw --container openclaw config ...) or podman exec openclaw ...; the same command from a fresh one-off container is rejected. On versions with gateway recover-lock, stop the gateway, then run openclaw gateway recover-lock to inspect and clear the stale ownership record before mutating config from outside the container. On those versions, heartbeat expiry alone does not prove the previous holder has stopped.
    • SELinux relabel fails on a bind mount (failed to change selinux label: insufficient permissions): Rootless Podman applies :Z/:z relabels as your user and cannot relabel host directories owned by root (for example a read-only /var/log/journal mount). Use a plain ro mount without a relabel suffix for system directories you don't own; reserve :Z for state directories your user owns. Note that dropping the relabel only avoids the relabel operation — it doesn't grant access by itself. The files keep their original SELinux labels, and reading them still fails if the container domain isn't permitted to that label. In that case give the container an appropriate policy or remove the mount entirely.
    • Container restarts after an image update: Run the one-off openclaw doctor --fix command in Upgrading images, then start the gateway again.
    • Container CLI commands hit the wrong target: Use openclaw --container <name> ... explicitly, or export OPENCLAW_CONTAINER=<name> in your shell.
    • openclaw update fails with --container: Expected. Rebuild/pull the image, then restart the container or the Quadlet service.
    • Quadlet service does not start: Run systemctl --user daemon-reload, then systemctl --user start openclaw.service. On headless systems you may also need sudo loginctl enable-linger "$(whoami)".
    • SELinux blocks bind mounts: Leave the default mount behavior alone; the launcher auto-adds :Z on Linux when SELinux is enforcing or permissive.
    Was this useful?