Containers

Docker

Docker is optional. Use it for an isolated, throwaway Gateway environment or a host without local installs. If you already develop on your own machine, use the normal install flow instead.

The default Docker sandbox backend uses only the docker CLI. Set the backend to "podman" to select native Podman directly. Sandboxing is off by default and does not require the Gateway itself to run in a container. SSH and OpenShell sandbox backends are also available; see Sandboxing.

Hosting multiple users? See Multi-tenant hosting for the one-cell-per-tenant model.

Prerequisites

  • Docker Desktop (or Docker Engine) + Docker Compose v2
  • At least 6 GB RAM for a local source image build; pre-built images avoid this build requirement
  • Enough disk for images and logs
  • On a VPS/public host, review Security hardening for network exposure, especially the Docker DOCKER-USER firewall chain

Containerized Gateway

  • Build the image

    From the repo root:

    bash
    ./scripts/docker/setup.sh

    This builds the Gateway image locally as openclaw:local. To use a pre-built image instead:

    bash
    export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"./scripts/docker/setup.sh

    Pre-built images are published first to the GitHub Container Registry. GHCR is the primary registry for release automation, pinned deployments, and provenance checks. The same release publishes a Docker Hub mirror at openclaw/openclaw:

    bash
    export OPENCLAW_IMAGE="openclaw/openclaw:latest"./scripts/docker/setup.sh

    Use ghcr.io/openclaw/openclaw or openclaw/openclaw and avoid unofficial mirrors, which don't share OpenClaw's release timing or retention policy. Version-specific tags include releases such as 2026.9.3 and prereleases such as 2026.9.1-beta.1. Stable releases move latest and main; trailing-month Gateway releases move only extended-stable. Variants include slim, main-slim, extended-stable-slim, latest-browser, main-browser, and extended-stable-browser. The default images bundle the codex and diagnostics-otel plugins. A -browser variant also ships with Chromium baked in for the Gateway-controlled browser. The agent sandbox browser uses a separate image.

  • Airgapped rerun

    On offline hosts, transfer and load the image first:

    bash
    docker load -i openclaw-image.tarexport OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"./scripts/docker/setup.sh --offline

    --offline verifies OPENCLAW_IMAGE already exists locally, disables implicit Compose pulls/builds, then runs the normal flow: .env sync, permission fixes, onboarding, Gateway config sync, Compose startup.

    If OPENCLAW_SANDBOX=1, offline setup also checks the configured default and per-agent sandbox images on the daemon behind OPENCLAW_DOCKER_SOCKET, including the browser-contract label on Docker-backed browser images. If a required image is missing or stale, setup exits without changing sandbox config rather than reporting a broken success.

  • Complete onboarding

    The setup script runs onboarding automatically:

    • prompts for provider API keys
    • generates a Gateway token and writes it to .env
    • creates the legacy auth-profile secret key directory
    • starts the Gateway via Docker Compose

    Pre-start onboarding and config writes run through openclaw-gateway directly (with --no-deps --entrypoint node), since openclaw-cli shares the Gateway's network namespace and only works once the Gateway container exists.

  • Open the Control UI

    Open http://127.0.0.1:18789/ and paste the token written to .env into Settings. If you switched the container to password auth, use that password instead.

    Need the URL again?

    bash
    docker compose run --rm openclaw-cli dashboard --no-open

    With a custom OPENCLAW_GATEWAY_PORT, replace port 18789 in the printed URL with your host port before opening it in the browser; keep the rest of the URL intact. Dashboard commands inside either container use the internal listener port.

  • Configure channels (optional)

    bash
    # WhatsApp (QR)docker compose run --rm openclaw-cli channels login # Telegramdocker compose run --rm openclaw-cli channels add --channel telegram --token "<token>" # Discorddocker compose run --rm openclaw-cli channels add --channel discord --token "<token>"

    Docs: WhatsApp, Telegram, Discord

  • Using the Control UI browser

    The Control UI Browser panel displays a browser controlled by the Gateway. It is separate from the browser on your laptop or phone that opens the dashboard. For a local managed browser with a Docker Gateway, Chromium must be available inside the Gateway container.

    For a new installation, use the official browser-equipped image with the normal Compose setup; no custom Dockerfile is needed:

    bash
    export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest-browser"./scripts/docker/setup.sh

    For a pinned deployment, choose a release's -browser tag instead of the moving latest-browser tag. For an existing Compose installation, change OPENCLAW_IMAGE in its .env to the browser variant, then pull and recreate the Gateway using the same Compose files and overlays as your current deployment:

    bash
    docker compose pull openclaw-gateway openclaw-clidocker compose up -d openclaw-gateway

    Keep your existing volumes, ports, and other settings. Do not rerun setup with an empty shell environment to change only the image: setup rewrites .env from the current shell and defaults.

    An existing home volume or bind mount covering /home/node/.cache/ms-playwright can hide the image's bundled Chromium. If browser discovery still fails after the image switch, check those mounts. Preserve the data, then either provision Chromium in the mounted home or adjust the mounts to leave the image's browser cache visible; recreating the container does not refresh a populated home volume.

    For a new installation from a local source build, bake Chromium into the image:

    bash
    OPENCLAW_IMAGE=openclaw:local OPENCLAW_INSTALL_BROWSER=1 ./scripts/docker/setup.sh

    This build-time option installs Chromium and Xvfb; setting it on an already-built container does not install a browser.

    The image supplies Chromium, not a replacement for your browser configuration:

    • Keep browser control enabled (browser.enabled). Use a local managed profile such as openclaw for the container's Chromium, not an extension, attach-only, or remote-CDP profile intended for another browser.
    • OpenClaw auto-detects the image's Playwright-managed Chromium on Linux. An explicit browser.executablePath or profile executable path must point to a binary inside the container; a path from your laptop will not work there.
    • A headless container needs headless browser operation. Check explicit browser.headless, profile headless settings, and OPENCLAW_BROWSER_HEADLESS overrides if startup reports a missing display. See Browser configuration.
    • Connect with operator.admin access to a Gateway advertising browser.request. Open + → Browser in the Chat side panel, navigate to a page, and confirm that its snapshot loads and navigation works. Loading the dashboard alone does not verify that Chromium can start.

    This is not the separate sandboxed browser container used by sandboxed agent sessions. Selecting a Gateway -browser image does not build or configure that sandbox image.

    Headless bootstrap

    For an unattended container host, put provider, Gateway, and channel credentials in the Compose .env file so both the one-shot bootstrap container and the long-running Gateway receive the same values:

    bash
    OPENAI_API_KEY=<provider-key>OPENCLAW_GATEWAY_TOKEN=<gateway-token>TELEGRAM_BOT_TOKEN=<bot-token>

    Run onboarding and channel provisioning without a pseudo-TTY, then start the Gateway:

    bash
    docker compose run -T --rm --no-deps --entrypoint node openclaw-gateway \  dist/index.js onboard --non-interactive --accept-risk --skip-health \  --mode local \  --auth-choice openai-api-key \  --secret-input-mode ref \  --gateway-auth token \  --gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN \  --skip-channels \  --no-install-daemondocker compose run -T --rm --no-deps --entrypoint node openclaw-gateway \  dist/index.js channels add --channel telegram --use-envdocker compose up -d openclaw-gateway

    The channel command fails before changing config if a plugin-declared environment variable is missing. Keep TELEGRAM_BOT_TOKEN in .env after bootstrap: --use-env leaves credential lookup to the environment without copying the token into openclaw.json, and the running Gateway needs the same variable. When channel config changes after startup, the Gateway's config watcher hot-reloads the affected channel automatically.

    See openclaw channels for credential-flag alternatives and other channel plugins.

    Manual flow

    bash
    BUILD_GIT_COMMIT="$(git rev-parse HEAD)"BUILD_TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"docker build \  --build-arg "GIT_COMMIT=${BUILD_GIT_COMMIT}" \  --build-arg "OPENCLAW_BUILD_TIMESTAMP=${BUILD_TIMESTAMP}" \  -t openclaw:local -f Dockerfile .docker compose run --rm --no-deps --entrypoint node openclaw-gateway \  dist/index.js onboard --mode local --no-install-daemondocker compose run --rm --no-deps --entrypoint node openclaw-gateway \  dist/index.js config set --batch-json '[{"path":"gateway.mode","value":"local"},{"path":"gateway.bind","value":"lan"},{"path":"gateway.controlUi.allowedOrigins","value":["http://localhost:18789","http://127.0.0.1:18789"]}]'docker compose up -d openclaw-gateway

    The Docker context excludes .git. Pass the source identity as build arguments as shown above so the image's About screen reports the checked-out commit and one build timestamp. scripts/docker/setup.sh resolves and passes both values automatically.

    Upgrading container images

    When you replace the OpenClaw image but keep the same mounted state/config, the new Gateway runs startup-safe upgrade migrations and plugin convergence before readiness. Routine image upgrades should not require a separate openclaw doctor --fix pass.

    If startup cannot complete those repairs safely, the Gateway exits instead of reporting healthy. With a restart policy, Docker, Podman, or Kubernetes may show the Gateway container restarting. Keep the mounted state volume, then run the same image once with openclaw doctor --fix as the container command, using the same state/config mounts the Gateway uses:

    bash
    docker run --rm -v <openclaw-state>:/home/node/.openclaw <image> openclaw doctor --fixpodman run --rm -v <openclaw-state>:/home/node/.openclaw <image> openclaw doctor --fix

    After doctor finishes, restart the Gateway container with its default command. In Kubernetes, run the same command in a one-off Job or debug pod mounted to the same PVC, then restart the Deployment or StatefulSet.

    After the container is running again, run the read-only deployment preflight against the same mounted state:

    bash
    docker compose run --rm openclaw-cli doctor --json

    Source-built images with selected plugins

    OPENCLAW_EXTENSIONS selects plugin manifest ids from the source checkout; existing source-directory names are also accepted when they differ. The Docker build resolves the selection to source directories once, installs production dependencies, links each selected plugin's own runtime dependencies under its packaged root in /app/dist/extensions/<id>, and includes the selected plugin runtime in the image. Source checkouts also compile first-party plugins published separately with openclaw.build.bundledDist: false; that marker still preserves the plugin's external npm or ClawHub ownership and does not change either artifact contract. Unknown, invalid, or ambiguous ids fail the image build. This includes WhatsApp: OPENCLAW_EXTENSIONS=whatsapp compiles and packages its runtime. Ordinary source builds generate its runtime through the separate external-plugin build path; root npm artifacts continue to exclude it. Selected plugins must compile successfully; unselected external plugin source and runtime output are pruned.

    For example, these commands build separate, multi-architecture standalone FakeCo Gateway images for ClickClack, Slack, and Microsoft Teams. ClawRouter is already part of the root OpenClaw runtime, so the ClickClack image selects only clickclack. The explicit empty browser argument keeps the default image free of Chromium:

    bash
    SOURCE_SHA="$(git rev-parse HEAD)"BUILD_TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"REGISTRY="registry.example.com/fakeco" build_gateway_image() {  gateway="$1"  selected_plugin="$2"  docker buildx build \    --platform linux/amd64,linux/arm64 \    --build-arg "GIT_COMMIT=${SOURCE_SHA}" \    --build-arg "OPENCLAW_BUILD_TIMESTAMP=${BUILD_TIMESTAMP}" \    --build-arg "OPENCLAW_EXTENSIONS=${selected_plugin}" \    --build-arg OPENCLAW_INSTALL_BROWSER= \    --provenance=mode=max \    --sbom=true \    --tag "${REGISTRY}/openclaw-${gateway}:${SOURCE_SHA}" \    --push \    .} build_gateway_image clickclack clickclackbuild_gateway_image slack slackbuild_gateway_image teams msteams

    Use --platform linux/arm64 --load or --platform linux/amd64 --load for a single native local build. Multi-platform output and attached SBOM/provenance require a registry or another Buildx output that preserves attestations. After pushing, inspect the manifest and deploy the immutable digest rather than the mutable source-SHA tag:

    bash
    docker buildx imagetools inspect \  "${REGISTRY}/openclaw-clickclack:${SOURCE_SHA}"# Deploy: registry.example.com/fakeco/openclaw-clickclack@sha256:<manifest-digest>

    These images are for standalone OCI-based Gateways and generic Docker users. Crabhelm-managed Gateways do not consume them: that delivery path builds a separate x86_64 appliance archive containing an OpenClaw npm tarball and pins the Node, archive, and manifest digests. Build that appliance independently from the same landed OpenClaw source.

    To test bundled plugin source against a packaged image, mount one plugin source directory over its packaged source path, e.g. OPENCLAW_EXTRA_MOUNTS=/path/to/fork/extensions/synology-chat:/app/extensions/synology-chat:ro. That overrides the matching compiled /app/dist/extensions/synology-chat bundle for the same plugin id. Restart the Gateway after adding or changing a mount; runtime loading and setup use the mounted source.

    Observability

    OpenTelemetry export is outbound from the Gateway container to your OTLP collector; it needs no published Docker port. To include the bundled exporter in a locally built image:

    bash
    export OPENCLAW_EXTENSIONS="diagnostics-otel"export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4318"export OTEL_SERVICE_NAME="openclaw-gateway"./scripts/docker/setup.sh

    Official prebuilt images already bundle diagnostics-otel; install clawhub:@openclaw/diagnostics-otel yourself only if you removed it. To enable export, allow and enable the diagnostics-otel plugin in config, then set diagnostics.otel.enabled=true (see the full example in OpenTelemetry export). Collector auth headers go through diagnostics.otel.headers, not Docker environment variables.

    Prometheus metrics reuse the already-published Gateway port. Install clawhub:@openclaw/diagnostics-prometheus, enable the diagnostics-prometheus plugin, then scrape:

    text
    http://<gateway-host>:18789/api/diagnostics/prometheus

    The route is protected by Gateway authentication; don't expose a separate public /metrics port or unauthenticated reverse-proxy path. See Prometheus metrics.

    Health checks

    Container probe endpoints (no auth required):

    bash
    curl -fsS http://127.0.0.1:18789/healthz   # livenesscurl -fsS http://127.0.0.1:18789/startupz  # startup and traffic admissioncurl -fsS http://127.0.0.1:18789/readyz    # deep, channel-aware readiness

    The image's built-in HEALTHCHECK pings /healthz; repeated failures mark the container unhealthy so orchestrators can restart or replace it. Use /startupz for an orchestrator startup or readiness probe so a failed channel account does not remove the otherwise healthy Gateway and Control UI from service. Use /readyz for monitoring that intentionally treats hard channel failures as not ready. See Health checks for response details.

    Authenticated deep health snapshot:

    bash
    docker compose exec openclaw-gateway sh -lc 'node dist/index.js gateway health --token "$OPENCLAW_GATEWAY_TOKEN"'

    Detailed topics

    • Install Overview — all installation methods
    • Podman — Podman alternative to Docker
    • Kubernetes — a minimal Kustomize starting point for running the Gateway on a cluster
    • Ansible — automated server deployment with Tailscale VPN and firewall isolation
    • Cloudflare Containers — experimental Worker plus container deployment with Litestream backups to R2
    • Updating — keeping OpenClaw up to date
    • Configuration — Gateway configuration after install
    Was this useful?
    On this page

    On this page