OpenClaw·Docker·Docker Compose

This OpenClaw Docker setup uses release v2026.9.8, source commit fc23bc864e4553c2d215e479eeec47b67a0bf943, and the official image ghcr.io/openclaw/openclaw:2026.9.8. The release record identifies the source revision; the Docker guide at that revision supplies the installation contract.

Verification boundary — October 3, 2026: commands and configuration were reviewed against upstream source. Installation, container health, device pairing and inference are not-run by AIHackers. This is a source-checked procedure, not a production-readiness certification. The owner smoke test below requires no provider key or model request.

Docker is not enough

The Gateway container can reach its mounted state, workspace and outbound network. Containerizing the Gateway does not enable OpenClaw’s separate agent sandbox; that feature is off by default. Use a dedicated disposable host or VM without personal credentials, work accounts or sensitive services. Read OpenClaw architecture risk and YOLO Safely before granting tools or connecting channels.

This path retains the official non-root runtime, capability drops and no-new-privileges. It adds no privileged mode, host networking, Docker-socket mount or host-root monitoring sidecar. It also does not promise a read-only root filesystem or network isolation: those need separately verified configuration.

Prerequisites

  • Docker Engine 28.0.0 or newer, including the Engine used by Docker Desktop, with a working daemon and permission to use it. Check the Server version in docker version. Docker documents a localhost-publishing exposure to other hosts on the same layer-2 network in older Engines. This path assumes the default NAT bridge network with no direct-routing overrides; see Docker’s port-publishing guidance. Docker access is a powerful host permission; use an existing authorized installation.
  • Docker Compose v2.24.1 or newer. OpenClaw says v2; this guide’s floor follows the tagged file’s optional env_file and colon-separated extra_hosts syntax in Docker’s service reference. Use docker compose, not the retired docker-compose command.
  • Git, Bash, curl, and OpenSSL or Python 3 for token generation. Use a Bash terminal on Linux/macOS or an appropriately configured WSL2 environment.
  • Free disk for the repository, image extraction, state, logs and backups. The prebuilt image avoids the upstream 6 GB RAM requirement for a local source build; upstream does not give a universal runtime memory minimum.
  • An unused host port 18789 and a new checkout/data directory. Do not run this fresh-install sequence over an existing deployment.

Check the existing environment:

1
2
3
4
docker version
docker compose version
git --version
bash --version

Quick start: acquire the complete pinned checkout

Use a fresh terminal without inherited OpenClaw overrides. Run this from the directory where you want to keep the deployment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
git clone --depth 1 --branch v2026.9.8 \
  https://github.com/openclaw/openclaw.git openclaw-2026.9.8
cd openclaw-2026.9.8
test "$(git rev-parse HEAD)" = fc23bc864e4553c2d215e479eeec47b67a0bf943
test -f docker-compose.yml
test -f .env.example
test -f scripts/docker/setup.sh

umask 077
cp .env.example .env
chmod 600 .env

Stop if any check fails. The complete checkout supplies .env.example, Compose and the setup script’s helper files. Downloading only docker-compose.yml does not provide them. Do not fill the provider-key examples yet; first verify the Gateway without credentials.

Narrow host publishing before bootstrap

The tagged Compose file publishes ports 18789, 18790 and 3978 on all host interfaces by default. This minimal Control UI path publishes only 18789 on host IPv4 loopback. Bridge and Microsoft Teams ingress stay unpublished.

Apply these edits to the pinned base file before running setup:

1
2
3
4
5
6
7
sed \
  -e 's|"${OPENCLAW_GATEWAY_PORT:-18789}:18789"|"127.0.0.1:${OPENCLAW_GATEWAY_PORT:-18789}:18789"|' \
  -e '/"${OPENCLAW_BRIDGE_PORT:-18790}:18790"/d' \
  -e '/"${OPENCLAW_MSTEAMS_PORT:-3978}:3978"/d' \
  docker-compose.yml > docker-compose.loopback.yml
mv docker-compose.loopback.yml docker-compose.yml
git diff -- docker-compose.yml

The resulting Gateway port block must be:

1
2
    ports:
      - "127.0.0.1:${OPENCLAW_GATEWAY_PORT:-18789}:18789"

This is a fragment of the upstream service, not a replacement Compose file. Keep its image, mounts, command and healthcheck. Setup uses explicit -f docker-compose.yml arguments and does not automatically load a normal docker-compose.override.yml; putting a safer port list only in that override would leave the first startup exposed. See the setup implementation.

Host publishing and container binding are different. 127.0.0.1:18789:18789 restricts the published host endpoint. Inside the Docker network, keep OPENCLAW_GATEWAY_BIND=lan, which listens on the container interfaces so port forwarding works. Setting it to loopback restricts the listener to the container’s own network namespace and prevents normal host access. lan is a bind mode, not a request to expose the host publicly. Other containers on the same Docker network remain within the trust boundary. See upstream networking and storage.

Environment and persistent state

Still in the checkout, select the versioned image and new directories outside Git:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
export OPENCLAW_IMAGE=ghcr.io/openclaw/openclaw:2026.9.8
export OPENCLAW_GATEWAY_PORT=18789
export OPENCLAW_GATEWAY_BIND=lan
export OPENCLAW_CONFIG_DIR="$PWD/../openclaw-data/state"
export OPENCLAW_WORKSPACE_DIR="$OPENCLAW_CONFIG_DIR/workspace"
export OPENCLAW_AUTH_PROFILE_SECRET_DIR="$PWD/../openclaw-data/legacy-secrets"
mkdir -p "$OPENCLAW_WORKSPACE_DIR" "$OPENCLAW_AUTH_PROFILE_SECRET_DIR"
chmod 700 "$PWD/../openclaw-data" "$OPENCLAW_CONFIG_DIR" \
  "$OPENCLAW_WORKSPACE_DIR" "$OPENCLAW_AUTH_PROFILE_SECRET_DIR"

docker compose config --quiet
OPENCLAW_SKIP_ONBOARDING=1 ./scripts/docker/setup.sh
chmod 600 .env

OPENCLAW_GATEWAY_PORT stays numeric; do not put 127.0.0.1:18789 in that variable because setup also uses it to construct browser origins. The port restriction belongs in Compose.

The script pulls the chosen image, creates/reuses a random Gateway token in .env, repairs permissions, sets local Gateway mode and allowed UI origins, then starts openclaw-gateway. Its OPENCLAW_SKIP_ONBOARDING=1 branch skips the interactive provider wizard. This installs the Gateway without configuring a model or channels. The script briefly uses a root container to repair the dedicated data-directory ownership; the long-running image runs as UID 1000, not root. Do not point these directories at existing personal files.

Host variableContainer mountWhat persists
OPENCLAW_CONFIG_DIR/home/node/.openclawopenclaw.json, shared and agent databases, credentials and plugin state
OPENCLAW_WORKSPACE_DIR/home/node/.openclaw/workspaceAgent workspace files
OPENCLAW_AUTH_PROFILE_SECRET_DIR/home/node/.config/openclawKey material for recovering legacy encrypted auth profiles

Keep .env, state and backups private. Current OAuth tokens can be plaintext in SQLite; the separate legacy-key mount does not encrypt those rows. Environment files also remain readable to authorized Docker operators. Never commit keys, publish dashboard links, or paste full docker compose config, container environment dumps or unsanitized logs into an issue. config --quiet validates without printing the expanded environment.

Keep the modified Compose file, project .env and these data paths together in your maintenance records. For daily commands, run from this checkout. This guide uses only the base Compose file, with no extra mounts or sandbox overlays.

Readiness and the smallest owner smoke test

Before starting the container

The setup command above starts containers. Before running it, confirm the new directories, exact source SHA and loopback-only port edit. Keep provider keys blank, channels disconnected and the host away from sensitive networks. Docker-published ports require Docker-aware firewall rules; ordinary UFW rules alone are not an exposure guarantee. Review OpenClaw’s network security guidance for the host you actually use.

After starting the container

1
2
3
4
5
6
7
8
docker compose ps
docker compose port openclaw-gateway 18789
docker compose exec openclaw-gateway id
curl -fsS http://127.0.0.1:18789/healthz
curl -fsS http://127.0.0.1:18789/startupz
curl -fsS http://127.0.0.1:18789/readyz
docker image inspect ghcr.io/openclaw/openclaw:2026.9.8 \
  --format '{{json .RepoDigests}}'

Allow startup time and repeat failed probes while inspecting logs. Expected observations: a running Gateway whose health becomes healthy, published address 127.0.0.1:18789, UID 1000, and successful liveness/startup responses. /readyz is a deeper, channel-aware check; investigate a non-200 response rather than treating it as interchangeable with liveness. The official Compose healthcheck invokes dist/docker-healthcheck.js, not curl on an invented port. unhealthy alone does not make Docker’s unless-stopped policy restart a still-running process. Upstream health checks explain the probes.

Open http://127.0.0.1:18789/ on the Docker host and enter the token from .env privately. To retrieve a dashboard link:

1
2
docker compose run --rm openclaw-cli dashboard --no-open
docker compose run --rm openclaw-cli devices list

If pairing is required, inspect the request and approve only your browser:

1
docker compose run --rm openclaw-cli devices approve <requestId>

Replace <requestId> with that inspected request’s ID. The CLI shares the Gateway network namespace and requires the Gateway container to exist. For a remote host, use an SSH tunnel from your local computer; replace user@host with your host:

1
ssh -N -L 18789:127.0.0.1:18789 user@host

Then open the same loopback URL locally. Keep the token private and do not disable device authentication or expand allowed origins to make an error disappear.

For the owner receipt, record the source SHA, image digest, Docker/Compose versions, port result, UID, all three probe outcomes and whether the UI/device connection succeeded. Recreate the Gateway and repeat the checks to verify mounted state survives; finish with the cleanup command below. Do not send a chat message. This proves only the observed Gateway behavior, not provider access, agent isolation or production suitability.

Add a provider only after the Gateway smoke test

Before configuring credentials, restrict the initial tool profile using upstream tool policy:

1
2
3
4
5
6
7
docker compose run --rm openclaw-cli config set tools.profile minimal
docker compose run --rm openclaw-cli config set tools.deny '["gateway"]' --strict-json
docker compose run --rm openclaw-cli config validate
docker compose run --rm openclaw-cli onboard \
  --mode local --no-install-daemon --gateway-auth token \
  --gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN \
  --skip-ui --suppress-gateway-token-output

The minimal profile includes the Gateway update tool, so the explicit denial removes that capability too. This is a restricted starting policy, not a sandbox. In the wizard, choose only a provider and model you are authorized to use; leave channels and optional skills unconfigured. Review where credentials are stored and prefer supported environment references when offered. Local onboarding otherwise defaults an unset tool profile to full; it preserves an explicitly configured profile. Review the resulting policy before any agent turn.

For an environment reference, edit .env privately with the exact variable from that provider’s documentation. After changing .env, recreate the Gateway so it receives the new environment:

1
2
docker compose up -d --force-recreate openclaw-gateway
docker compose run --rm openclaw-cli models status

Status is configuration/auth inspection, not a successful model call. Live probes and chat can incur provider charges; do them only with a separate budget and acceptance test.

Correction to the earlier example: Gemma and Gemini are different model families. GEMINI_API_KEY is supported for the Google Gemini provider, but the old “Gemma 4” comment beside gemini-2.5-pro-exp-03-25 did not identify a reproducible Gemma setup. This guide removes that stale model and free-tier claim. OpenClaw selects models through agents.defaults.model.primary or models set, not the earlier DEFAULT_MODEL recipe. See the pinned Google provider guide and model CLI.

The earlier ENABLE_SHELL_EXECUTION, ENABLE_FILE_WRITE, ENABLE_NETWORK_FETCH, GATEWAY_BIND, GATEWAY_PORT, LOG_LEVEL and LOG_FORMAT block did not establish working OpenClaw controls. Use documented configuration and OPENCLAW_* variables instead of relying on those safety switches. No automatic trial-provider fallback is configured here.

Common failures and mistakes

  • Missing .env.example or helper script: acquire the complete pinned checkout; stop rather than inventing a file from an unrelated release.
  • Compose schema errors: check the Compose version and run docker compose config --quiet. Do not remove authentication or capability controls to bypass validation.
  • Image pull fails: confirm the official image/tag and registry access. Do not silently switch to latest or an unofficial mirror.
  • EACCES on mounted state: the image needs UID 1000 write access to the dedicated state/workspace/key directories. Inspect those paths and their ownership; do not make them world-writable or run the Gateway permanently as root.
  • Port already in use: stop this setup and choose an intentional alternate numeric host port. Update the publishing check, browser URL, SSH tunnel and configured UI origins together; internal port 18789 remains unchanged.
  • Unreachable UI: distinguish host publishing from the container listener. Verify the loopback port result and lan bind, then liveness/startup probes and local logs.
  • Unauthorized or pairing required: verify the current .env token, use the dashboard/device commands above, and approve only a recognized request. See tagged troubleshooting.
  • Model missing or provider auth fails: inspect the selected provider route and credential source. A configured model or a healthy Gateway is not proof of an inference response.

Privileged mode

Do not add privileged mode or extra capabilities to repair a startup error. Preserve the upstream NET_RAW/NET_ADMIN drops and no-new-privileges on both services.

Docker-socket mounts

Do not enable OPENCLAW_SANDBOX=1 or uncomment the socket mount in this path. That optional upstream Docker sandbox flow grants the Gateway additional Docker access and needs a separate trust review.

Binding to all interfaces

Removing 127.0.0.1 from the published host port widens exposure. Keeping the container bind at lan is necessary for this bridge-network installation. Check both rather than treating them as one setting.

Running beside personal credentials

Use a separate host or VM. Do not mount SSH keys, cloud credentials, host root or work-account directories. A loopback UI does not restrict outbound agent activity or protect shared host services.

Monitoring container activity

1
2
docker compose logs --tail 100 openclaw-gateway
docker compose stats --no-stream openclaw-gateway

Inspect logs locally; redact tokens, account IDs and workspace content before sharing. These commands observe this Compose project without adding a sidecar with access to host root.

Stop, cleanup and rollback

Stop and remove this project’s containers/network while retaining the host bind-mounted data:

1
docker compose down

Do not use a global Docker prune. down does not remove the checkout, .env, openclaw-data or cached image. Keep them until the owner decides what to retain; delete only those verified deployment paths when intentionally discarding its state and secrets.

Before an upgrade, stop the Gateway and take a protected backup of the whole state/workspace, separate legacy-key directory, project .env and modified Compose file. Record the working source SHA and image digest. The official entrypoint can migrate databases on image replacement, so rolling back may require the matching pre-upgrade state, not just an older image. Preserve migration backups and lock/recovery artifacts. Upstream upgrade behavior describes this boundary.

To roll back a later upgrade: stop that deployment, preserve its failed state separately, restore the matching protected backup to the original paths, and restore its .env/Compose and pinned source/image. Use a fresh terminal with no inherited OpenClaw or provider overrides, in the restored checkout. Clear the variables this installation sequence exported before starting:

1
2
3
4
unset OPENCLAW_IMAGE OPENCLAW_GATEWAY_PORT OPENCLAW_GATEWAY_BIND \
  OPENCLAW_CONFIG_DIR OPENCLAW_WORKSPACE_DIR OPENCLAW_AUTH_PROFILE_SECRET_DIR
docker compose config --quiet
docker compose up -d openclaw-gateway

Compose takes shell variables ahead of .env. Restoring .env, or supplying --env-file, does not override a stale exported image or mount path. Confirm the restored image, directories and loopback port, then repeat the readiness/exposure checks.

Do not rerun setup blindly for an image-only update: it rewrites .env from the current shell and defaults. Do not overwrite the loopback edit when changing source revisions; reapply and review it against the new Compose file before startup.

What remains to verify

This source review does not establish host firewall/egress policy, backup restoration, safe agent tools, a particular provider/model entitlement, or production readiness. Runtime installation and the owner receipt remain not-run. Those are concrete checks for the deployment owner, not promised future articles or calendar dates.