On this page
On this page
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 mutually untrusted users? Run a separate Gateway for each trust boundary, ideally under separate OS users or hosts. See Security trust 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-USERfirewall chain
Containerized Gateway
Build the image
From the repo root:
This builds the Gateway image locally as openclaw:local. To use a pre-built image instead:
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:
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:
--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?
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)
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:
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:
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:
OPENCLAW_INSTALL_BROWSER=1 installs full Chromium and headless shell, preserving
existing Playwright scripts that use default headless launches. Choose chromium
to install only full Chromium, or headless-shell for a smaller, headless-only image:
All modes use the repository-pinned Playwright installer, including its system
libraries, fonts, and FFmpeg helper. Empty or 0 omits the browser. These are
build-time choices; setting them on an already-built container does not install
or replace a browser. Headless shell cannot provide a headed browser window or
Chrome extensions. Keep full Chromium for those workflows and validate your sites
before switching. Neither browser distribution is MIT-only; inventory the actual
image and preserve all third-party license notices.
OpenClaw automatically discovers full Chromium only. A headless-shell image
requires an explicit shell executable path on a separate managed profile. Find
the installed path inside the container (the revision directory changes with
Playwright updates):
Set browser.profiles.shell.executablePath to that path, give the profile its own
cdpPort, and keep it headless. Select that profile when starting the browser.
Do not reuse an existing full Chromium profile: switching distributions can
discard localStorage. See Browser configuration.
With the explicit chromium mode,
your own Playwright scripts must use channel: "chromium" for headless launches;
Playwright's default headless launch expects the separate shell binary. Keep 1
to retain both binaries without changing existing scripts.
The bundled Diffs PNG/PDF renderer supports all image modes. After explicit and
system-browser choices, it selects Playwright's full Chromium when installed and
otherwise retains Playwright's default headless-shell selection.
The image supplies Chromium, not a replacement for your browser configuration:
- Keep browser control enabled (
browser.enabled). Use a local managed profile such asopenclawfor 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.executablePathor profile executable path must point to a binary inside the container; a path from your laptop will not work there. - Managed browsers run headless by default. Check explicit
browser.headless, profile headless settings, andOPENCLAW_BROWSER_HEADLESSoverrides if startup reports a missing display. See Browser configuration. - Connect with
operator.adminaccess to a Gateway advertisingbrowser.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:
Run onboarding and channel provisioning without a pseudo-TTY, then start the 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
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
image entrypoint runs openclaw doctor --fix --non-interactive under exclusive
maintenance ownership before starting the Gateway. This covers the default image
command and Compose's foreground Gateway command, including its selected profile.
Routine image upgrades do not require a separate Doctor pass.
Keep one Gateway replica per state volume, and stop the old container before starting its replacement. The startup repair needs exclusive maintenance ownership of that state; an overlapping Gateway can prevent it from completing. For Compose, use the same files and overlays as your running deployment:
In Docker Swarm, keep replicas: 1 and use update_config.order: stop-first and
rollback_config.order: stop-first. In Kubernetes, keep replicas: 1 and use a
Recreate Deployment strategy, as the provided manifest
does. A shared volume is not a way to scale Gateway replicas. Back up the state
before upgrading; see Backups.
Activation retains a full pre-repair copy of local state under
<state-dir>.update-captures/ the first time Doctor captures in a container.
Later starts of the same container (docker stop/docker start, restart
policies, host reboots) still run the Doctor repair pass but skip that repeated
full copy, keep it, and still retire other Doctor copies older than 30 days;
repair-specific backups still apply. If a capture fails, the next
start tries again. A new image, a recreated container, or a container whose
/tmp does not persist across restarts captures again.
Read-only container root
A read-only container root still needs writable runtime storage. For example,
with HOME=/state and OPENCLAW_STATE_DIR=/state, provide these mounts:
| Path | Purpose | Storage |
|---|---|---|
/state |
SQLite databases and sidecars, credentials, and state ownership locks under /state/tmp/openclaw-<uid>/ |
Persistent writable volume |
Configured workspace directories, such as /workspace |
Agent files and workspace migrations | Persistent writable volume |
/state/.cache or the absolute directory selected by XDG_CACHE_HOME |
Private SQLite snapshot staging | Writable volume or tmpfs |
/tmp |
Runtime temporary files and the container activation marker | Writable tmpfs |
/state.update-captures |
Retained pre-repair state captures | Separate persistent writable volume |
Set ownership so the container's UID/GID can create files and directories in each
writable location. Database files and their parent directories must be writable;
mounting only the database file writable is insufficient for SQLite sidecars.
Configured plugin storage or other file outputs may need additional mounts.
See Gateway lock for the lock paths.
TMPDIR does not relocate state ownership locks.
The capture path is a sibling of the resolved state directory, not a child. A
writable /state therefore does not make /state.update-captures writable. If
that sibling is unavailable, standalone Doctor reports that the original state
capture was unavailable and continues. This warning does not identify the cause
of a later fatal filesystem error. Keep the capture volume private: it contains
raw pre-repair state and credentials, and is excluded from normal backups and
support exports.
For a read-only config bind such as /run/config/openclaw.json, set
OPENCLAW_CONFIG_PATH=/run/config/openclaw.json and
OPENCLAW_CONFIG_READONLY=1 in both the Gateway and CLI container environments.
Doctor can still repair writable state, but config repairs must be applied in
your external deployment source. Invalid or legacy config that blocks startup
must be migrated there before restarting. See
Externally managed config.
If startup reports EROFS, inspect the path and operation in the error and check
that its mount is writable. Changing file modes cannot make a read-only mount
writable. Do not delete state or lock files to repair a mount error, and do not
disable native filesystem checks.
On older Linux hosts such as Synology DSM, an unavailable openat2 syscall can
make older images report that another Gateway owns even an empty state volume.
Current images use the guarded filesystem fallback; keep native filesystem
checks enabled. Doctor reports the underlying lock failure and recovery action
instead of treating every acquisition error as an active Gateway. Permission
errors require a writable state mount for the container user. If the filesystem
cannot provide exclusive file creation for state ownership, stop OpenClaw, back
up its state, and move the state volume to a local filesystem that supports it.
Ordinary database transactions use SQLite locking on that volume. Do not delete
state or lock files to bypass ownership.
Other CLI commands and help pass through unchanged. If you replace the image's entrypoint, run Doctor against the same mounted state/config before launching the Gateway; a custom entrypoint bypasses this activation step.
This includes agent database schema upgrades, shared-state audit migrations, and
legacy workspace setup imports. Before advancing database schemas, Doctor saves
verified SQLite copies beside the originals as
<database>.pre-startup-migration-<id>.bak. Fully declared table migrations may
instead use .scoped.bak archives; restore those with
openclaw database restore-migration-backup, never by replacing the database file.
See declared migration scopes.
The shared database and affected agent
databases use the same backup ID. Config backups and retired workspace-file
archives follow the normal Doctor repair rules. Keep these files with your
pre-upgrade backup; a rollback must restore the matching state as well as the old
image. See rollback.
Retries with the same candidate build, database files, and target schemas verify and reuse the
first completed backup group for the full database inventory, including after a
partially completed migration. A temporary .bak.capturing file records an
unfinished capture and is durably removed before migration starts. An interrupted
capture is recaptured; a missing or damaged member of a completed group stops
migration and preserves the surviving snapshots for recovery. Repeated
failed attempts therefore do not create another full backup group each time.
Older backup groups remain available for operator-managed rollback.
Doctor records these originals for openclaw update cleanup --dry-run. They stay
protected until Doctor verifies that the migration completed and a later update
finishes successfully. Cleanup can then retire the recorded group after your
confirmation. Older unrecorded .bak files are listed as protected. Keep these
files with your pre-upgrade backups while you still need the matching rollback.
On FUSE filesystems such as Unraid's shfs, a missing native no-replace rename
does not require an operator step in native auto mode when same-directory
hardlinks are supported. fs-safe preserves the source inode and bytes through
exclusive hardlink publication and unlink. Native require mode still refuses
the unsupported primitive. Doctor recovers interrupted source/claim pairs without
replacing another file. Its separate missing-addon/native-off compatibility path
syncs the source and directory before removing the old name; native moves do not
promise per-move crash durability. Archive hardening and durable recovery
checkpoints remain owned by Doctor.
SQLite backup verification rechecks snapshot bytes when FUSE modification or change timestamps drift, while still rejecting changed contents or file identities.
Readiness remains false while the default or system agent database is refused, and the readiness response includes the admission reason. A refused optional agent remains isolated while healthy agents can serve requests.
Missing or drifted required SQLite indexes are rebuilt by the schema migration owner before session startup completes. Repair warnings identify the agent, database path, rebuilt indexes, and elapsed time. Current-schema shape refusal reports list all affected databases in stable path order. Missing required tables, incompatible columns, and other changes that cannot be reconstructed safely still require Doctor; startup does not recreate a missing data table as an empty one.
On Linux hosts without file creation timestamps, normal SQLite writes and permission repairs can change the reported file birth time. These changes do not invalidate the open database; actual file replacement still stops admission.
Startup exits with code 78 when required state cannot be migrated safely:
for example, source identities conflict, data is unreadable, another writer owns
the state, or the filesystem provides no safe, durable way to publish a claim.
The retained source, claim, and backups are recovery inputs; do not delete them to
silence the error. If the filesystem lacks the required primitives, stop the
Gateway and expose the same data through its native backing filesystem before
retrying (for example, an Unraid pool path instead of the shfs share).
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:
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. First scale the Gateway workload to zero and wait for its pods to terminate; in Swarm, scale the Gateway service to zero and wait for its tasks to stop. Keep the workload stopped until Doctor finishes, then restore one replica. If ownership is still refused after the old container has stopped, follow Gateway lock recovery. Do not delete lock files or repeatedly restart competing containers.
After the container is running again, run the read-only deployment preflight against the same mounted state:
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.
Custom package builds using scripts/package-openclaw-for-docker.mjs --bundle-plugin
retain each selected plugin's declared npm bundles from its own installation,
including their installed runtime dependencies and local patches. Packaging does
not change the checkout's dependency links or shared package store.
After the frozen production install, Docker also composes the selected plugins'
exact production and optional pins into the final distribution manifest and
links their already-installed package identities at the root resolution scope.
This lets plugin-owned code emitted into shared root chunks resolve the same
packages while retaining the plugin-local links needed by plugin-root chunks.
The composition performs no second install or dependency resolution and fails
instead of replacing an incompatible root binding.
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.
The install layer stages every workspace's package.json so frozen-lockfile
validation can check the complete workspace. The build stage installs workspace
dependencies needed to compile plugin assets; the production stage installs only
core, shared packages, and required or selected plugins. Staging a plugin manifest
does not select that plugin for the runtime image.
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:
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:
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:
Official prebuilt images already bundle diagnostics-otel; install @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 @openclaw/diagnostics-prometheus, enable the diagnostics-prometheus plugin, then scrape:
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 check endpoints (no auth required):
The image's built-in HEALTHCHECK pings /healthz; repeated failures mark the container unhealthy so orchestrators can restart or replace it.
It uses the live Gateway lock's port first, then OPENCLAW_GATEWAY_PORT, then
the configured port. An explicit environment port still works when the config
cannot be loaded.
Use /startupz for an orchestrator startup or readiness check 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:
Detailed topics
The full variable table, apt/pip build extras, and build-memory tuning.
LAN vs loopback, host.docker.internal, Claude CLI, Bonjour, and mounted state.
Compose command table, sandbox/CI/DNS/EACCES accordions, and image refreshes.
Enabling the agent sandbox plus the Docker troubleshooting accordions.
- Environment variables
- LAN vs loopback
- Host local providers
- Claude CLI backend in Docker
- Bonjour / mDNS
- Storage and persistence
- ClawDock migration
- Image contents and security scanning
- Weekly image refreshes
- Running on a VPS?
- Agent sandbox
- Troubleshooting
Related
- 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
Build from an offline installed runtime
Release tooling can combine a portable OpenClaw package with an inventoried Linux/x64 dependency tree without rebuilding the package. Keep the package, dependency input, and source checkout immutable during composition. The source checkout must match the dependency input's package and lockfile hashes and the package's build identity; the tooling checkout has its own identity.
From the tooling checkout:
The output directory must not exist or overlap an input. The package receipt
supplies sha256, bytes, and buildInfo: { version, commit, buildId }.
The dependency root supplies runtime-input.json with platform, arch,
packageJsonSha256, and lockfileSha256. Its newline-delimited JSON inventory
uses paths prefixed with node-runtime-dependencies/linux-x64 and records each
entry's kind and octal mode, plus size/sha256 for files or target for
links. These input artifacts and their trusted digests come from the release
owner; this command does not install dependencies or authorize their source.
Composition preserves packaged bundled dependencies, materializes the remaining
importer-relative dependency closure, and stages the maintained Linux lifecycle
helpers. It writes app/, installed-runtime.tgz, a complete output inventory,
and receipt.json. Symlink permission bits can differ across input filesystems;
that count is recorded, while file/directory modes, content, targets and
containment remain strict. No input is modified.
Full-inventory checking remains the default. To reuse a prior strict composition,
add --qualified-dependency-receipt /path/to/prior/receipt.json and
--qualified-dependency-receipt-sha256 "$PRIOR_RECEIPT_SHA256". The release owner
must supply the trusted receipt digest; a caller-written operation report is not
qualification. The receipt must bind the same dependency manifest, source package,
lockfile, and target. Relocated donors are supported, but never trusted by location
or by a prior receipt alone: composition rechecks their complete topology, modes,
sizes, link targets/containment, and package manifests, then hashes the actual
selected copies against the qualified inventory. Unselected payload bytes are
not rehashed. The new receipt labels this qualified-reuse with
inputInventory: false; it cannot qualify another reuse.
Before passing the composition to Docker, verify it and obtain the maintained image recipe:
All six location/identity flags are required. --operation verify uses the same
flags. By default both operations verify the complete installed inventory.
They also check the tooling lockfile against the runtime
source lock, the Docker entrypoint against the original runtime commit's Git
blob, and the maintained final-stage COPY inputs. The runtime commit must be
available in the tooling checkout's Git object database.
For an archive-bound recipe, add --image-input archive to image-recipe. This
checks the receipt, archive, and manifest digests plus the small identity, root
module, and helper inputs used during admission, without rehashing all of app/.
Its archive-bound result is not a full directory verification. The recipe adds
OPENCLAW_INSTALLED_RUNTIME_INPUT=installed-runtime-archive-input and
OPENCLAW_INSTALLED_RUNTIME_SHA256: Docker copies the archive, verifies that exact
digest inside the build, and only then extracts it. Changes to unchecked app/
bytes cannot enter this route; a changed archive fails before extraction.
The JSON recipe names the maintained Dockerfile, openclaw-runtime target,
OPENCLAW_RUNTIME_ASSETS=installed-runtime-assets build argument, and
openclaw_installed_runtime named context pointing at the composition root.
Pass these to Buildx from the pinned tooling checkout. Keep the context unchanged
through consumption, then verify it again. The default source-build route is
unchanged. The installed-assets stage runs finalization with networking disabled;
it checks the native prebuild before lifecycle and never repairs missing app
dependencies with npm. Later shared image stages still install OS utilities and
operator tools and can require network access.
Composition and recipe verification are not Linux runtime qualification.
Receipts retain compositionOnly: true, ABI/libc requires-target-acceptance,
and lifecycle target-required. Native loading, lifecycle completion,
entrypoint, provider identity, and runtime export checks still require actual
Linux target acceptance.