Containerized OpenClaw Gateway setup with Docker and Docker Compose.
This repository provides:
- A production-oriented
Dockerfilebased on Ubuntu 24.04 - Automatic OpenClaw installation from the official installer
- A startup entrypoint that initializes
openclaw.jsonon first run - Persistent config/workspace volumes for local development and daily use
- A ready-to-run
docker-compose.ymlservice definition
Dockerfile: image build and gateway entrypoint scriptdocker-compose.yml: local runtime configurationscripts/build-image.sh: local image build/tag helper for Composescripts/create-tag.sh: local release tag creation helper.github/workflows/tag-build.yml: builds and pushes Docker image on tag push.github/workflows/sync-upstream-major.yml: manual workflow to sync latest upstream major tag and persist it in-repoOPENCLAW_UPSTREAM_VERSION: tracked upstream major version used by sync workflow.github/workflows/bats-tests.yml: runs bats unit tests for release and workflow guardrailstests/create-tag.bats: unit tests for release tag scripttests/build-image.bats: unit tests for local image build/tag scripttests/docker-compose.bats: unit tests for compose image-only guardrailstests/tag-build-workflow.bats: unit tests for Docker image publish workflow guardrailstests/sync-upstream-major-workflow.bats: unit tests for upstream version sync workflow guardrailsLICENSE: MIT license
- Docker Engine with Docker Compose v2, or Podman with
podman compose/podman-compose - Network access during image build (for package install and OpenClaw installer)
This project supports both Docker and Podman. The examples below use Docker by default; if you prefer Podman, set CONTAINER_RUNTIME=podman and use ./scripts/compose.sh.
- Build local image for the default tag (
latest):
./scripts/build-image.sh- Start service:
docker compose up -d
# Or use the wrapper script: ./scripts/compose.sh up -dThis mounts the full /home/node tree to the host through OPENCLAW_HOME_DIR.
- Check container status:
docker compose ps
# Or: ./scripts/compose.sh psInside the container, openclaw gateway status reports a healthy foreground runtime as Runtime: running (container foreground).
- Check gateway health endpoint:
curl http://127.0.0.1:18789/healthzIf you override OPENCLAW_GATEWAY_PORT, Compose now publishes the same port on both the host and the container.
- Read gateway logs:
docker compose exec openclaw-gateway tail -f /home/node/.openclaw/logs/openclaw.stdout.log
docker compose exec openclaw-gateway tail -f /home/node/.openclaw/logs/openclaw.stderr.log
# Or: ./scripts/compose.sh exec openclaw-gateway tail -f /home/node/.openclaw/logs/openclaw.stdout.log- Stop service:
docker compose down
# Or: ./scripts/compose.sh downClick to expand instructions
Podman supports rootless containers, so regular users can build, run, and manage them without sudo. To use Podman:
- Install Podman and a Compose frontend (example for Debian/Ubuntu). For other platforms, see the official Podman installation guide:
sudo apt-get install podman uidmap
python3 -m pip install --user podman-composeMake sure ~/.local/bin is in your PATH if you install podman-compose with --user.
- Set the
CONTAINER_RUNTIMEenvironment variable for your current shell:
export CONTAINER_RUNTIME=podman- Build the local image and manage the service with the provided helper scripts:
./scripts/build-image.sh
./scripts/compose.sh up -d
./scripts/compose.sh ps
./scripts/compose.sh downThe wrapper automatically uses podman compose when available and falls back to podman-compose.
Or run Podman Compose directly:
podman compose up -d
# Fallback: podman-compose up -dAfter the first startup, enter the container and run onboarding:
docker compose exec openclaw-gateway bash
# Or: ./scripts/compose.sh exec openclaw-gateway bash
openclaw onboardNotes for first-time setup:
- During onboarding, the gateway process may restart.
- If your current terminal session is interrupted, enter the container again and run
openclaw onboardagain. - Existing onboarding progress is reused from persisted config, so you only need to complete the remaining steps.
The container runtime identity is fixed to user node with home directory /home/node. These values are not configurable through environment variables.
The image and entrypoint ensure /home/node itself is owned by node:node, and the container-managed content under /home/node is expected to stay writable by the node user.
The current docker-compose.yml uses whole-home persistence:
./openclaw-home->/home/node
Start it with:
docker compose up -d
# Or: ./scripts/compose.sh up -dThis preserves runtime files outside .openclaw, such as .npm, .agents, .codex, and .cache, in the same host directory.
The container entrypoint creates these directories automatically when needed.
Gateway process output is redirected to files inside the state volume:
- stdout:
/home/node/.openclaw/logs/openclaw.stdout.log - stderr:
/home/node/.openclaw/logs/openclaw.stderr.log
Versions v2026.4.9 and earlier used a split layout like this:
OPENCLAW_CONFIG_DIR->/home/node/.openclawOPENCLAW_WORKSPACE_DIR->/home/node/.openclaw/workspace
To move that data into the current whole-home layout, stop the old container first and copy both the state directory and the separately mounted workspace into OPENCLAW_HOME_DIR:
docker compose down
mkdir -p ./openclaw-home/.openclaw
cp -a ./openclaw-data/. ./openclaw-home/.openclaw/
mkdir -p ./openclaw-home/.openclaw/workspace
cp -a ./openclaw-data/workspace/. ./openclaw-home/.openclaw/workspace/
docker compose up -dIf you used custom legacy host paths, replace ./openclaw-data above with your previous config and workspace directories before starting the current Compose setup.
If /home/node/.openclaw/openclaw.json does not exist, the entrypoint generates it with:
gateway.modefromOPENCLAW_INIT_GATEWAY_MODE(default:local)gateway.bindfromOPENCLAW_GATEWAY_BIND(default:lan)gateway.auth.tokenfromOPENCLAW_GATEWAY_TOKEN, or auto-generated when emptygateway.controlUi.allowedOriginsfromOPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS, orhttp://127.0.0.1:<port>by default
If a token is generated automatically, it is persisted in openclaw.json and reused on later starts.
You can place these in a .env file next to docker-compose.yml.
| Variable | Default | Description |
|---|---|---|
OPENCLAW_VERSION |
latest |
Runtime image tag in Compose (also used by scripts/build-image.sh when --tag is omitted) |
OPENCLAW_GATEWAY_BIND |
lan |
Gateway bind strategy passed to openclaw gateway --bind |
OPENCLAW_GATEWAY_PORT |
18789 |
Gateway HTTP port |
OPENCLAW_BRIDGE_PORT |
18790 |
Bridge port exposed by Compose |
OPENCLAW_GATEWAY_TOKEN |
empty | Gateway auth token. If empty and config missing, one is generated |
OPENCLAW_INIT_GATEWAY_MODE |
local |
Initial gateway.mode for generated config |
OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS |
auto | JSON array string for allowed control UI origins |
OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_ALLOW_HOST_HEADER_ORIGIN_FALLBACK |
false |
Initial fallback behavior in generated config |
OPENCLAW_STDOUT_LOG_PATH |
/home/node/.openclaw/logs/openclaw.stdout.log |
OpenClaw process stdout log file path |
OPENCLAW_STDERR_LOG_PATH |
/home/node/.openclaw/logs/openclaw.stderr.log |
OpenClaw process stderr log file path |
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS |
empty | Forwarded to container runtime environment |
OPENCLAW_HOME_DIR |
./openclaw-home |
Host directory mounted to /home/node |
CLAUDE_AI_SESSION_KEY |
empty | Optional key forwarded into container |
CLAUDE_WEB_SESSION_KEY |
empty | Optional key forwarded into container |
CLAUDE_WEB_COOKIE |
empty | Optional cookie forwarded into container |
If you did not set OPENCLAW_GATEWAY_TOKEN manually, inspect the generated config:
jq -r '.gateway.auth.token' ./openclaw-home/.openclaw/openclaw.jsonIf jq is not installed:
grep -n '"token"' ./openclaw-home/.openclaw/openclaw.jsonBuild image with helper script:
./scripts/build-image.sh --tag 2026.3.11.2Or build manually:
docker build --build-arg OPENCLAW_VERSION=2026.3.11 -t tenfyzhong/openclaw:2026.3.11.2 .When using Compose with a custom tag, use the same OPENCLAW_VERSION value:
OPENCLAW_VERSION=2026.3.11.2 docker compose up -dRun container directly:
docker run --rm -it \
-p 18789:18789 -p 18790:18790 \
-e OPENCLAW_GATEWAY_BIND=lan \
-v "$PWD/openclaw-home:/home/node" \
tenfyzhong/openclaw:2026.3.11.2 gatewayUse the script from repository root:
./scripts/create-tag.shOptional: force a specific major version:
./scripts/create-tag.sh --major 2026.3.11Script behavior:
- Always runs
git fetch --tags originfirst - Major version source is
openclaw/openclawrelease tags:vX.Y.ZvX.Y.Z-N(treated as majorvX.Y.Z)- Pre-release tags like
-beta.*are ignored
- If
--majoris provided, it must exist inopenclaw/openclaw - If local repo does not have
vX.Y.Z, it createsvX.Y.Z - If local repo already has
vX.Y.Z, it creates the next patch tagvX.Y.Z.N(auto increment) - It only creates local tag; push is manual
Push manually when ready:
git push origin <tag>Workflow: .github/workflows/tag-build.yml
- Trigger:
git pushof tag matchingv* - Docker tags pushed:
tenfyzhong/openclaw:<git-tag-without-v>tenfyzhong/openclaw:latest
- Build strategy:
- Runs architecture builds in parallel (
linux/amd64+linux/arm64) - Pushes temporary arch tags:
tenfyzhong/openclaw:<git-tag-without-v>-amd64tenfyzhong/openclaw:<git-tag-without-v>-arm64
- Publishes multi-arch manifest tags:
tenfyzhong/openclaw:<git-tag-without-v>tenfyzhong/openclaw:latest
- Runs architecture builds in parallel (
- Also creates a GitHub Release for the same tag
- Release notes include Docker image usage examples:
docker pull tenfyzhong/openclaw:<git-tag-without-v>OPENCLAW_VERSION=<git-tag-without-v> docker compose up -d- Direct
docker runcommand example
- Build arg
OPENCLAW_VERSIONalways uses major base (X.Y.Z)- Example: git tag
v2026.3.11.2builds withOPENCLAW_VERSION=2026.3.11 - Both tags are published as multi-arch manifest lists on Docker Hub
- Example: git tag
docker login -u "$DOCKERHUB_USERNAME"
docker buildx create --name openclaw-multiarch --driver docker-container --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg OPENCLAW_VERSION=2026.3.11 \
-t tenfyzhong/openclaw:2026.3.11-local \
--push \
.If buildx builder already exists, reuse it and skip docker buildx create.
Workflow: .github/workflows/sync-upstream-major.yml
Run manually from GitHub:
- Open repository
Actions - Select
Sync Latest Upstream Major Tag - Click
Run workflow
Behavior:
- Fetches latest stable major tag from
openclaw/openclaw - Uses
OPENCLAW_UPSTREAM_VERSIONto track the synced upstream major version in git - If latest upstream major changes:
- Updates
OPENCLAW_UPSTREAM_VERSION - Commits and pushes this version update to the selected branch
- Creates and pushes the same version tag from that commit
- Updates
- If version file is already current, it only checks whether the tag exists
- Pushed tag triggers
tag-build.ymlto build/push Docker image
Configure repository secrets in Settings -> Secrets and variables -> Actions:
DOCKERHUB_USERNAME: Docker Hub usernameDOCKERHUB_TOKEN: Docker Hub access token (fordocker/login-action)RELEASE_PUSH_TOKEN: GitHub token used by manual sync workflow to push tags
Recommended: Fine-grained personal access token.
- GitHub avatar ->
Settings Developer settings->Personal access tokens->Fine-grained tokens- Click
Generate new token - Set token name and expiration
Repository access: select only this repositoryRepository permissions:Contents:Read and writeMetadata:Read-only(default)
- Generate token and copy it immediately
- Go back to repository
Settings->Secrets and variables->Actions New repository secret- Name:
RELEASE_PUSH_TOKEN - Value: the generated token
After saving, rerun Sync Latest Upstream Major Tag workflow.
bats tests/*.batsCI workflow Bats Unit Tests runs automatically on:
- All pull requests targeting
main - Pushes to
main
To require CI success before merge and block direct pushes:
- Go to repository
Settings->Branches->Add branch protection rule - Set
Branch name patterntomain - Enable
Require a pull request before merging - Enable
Require status checks to pass before merging - Select status check
Bats Unit Tests / bats - Enable
Require branches to be up to date before merging(recommended) - Enable
Include administrators(recommended) - Disable direct push by enabling
Restrict who can push to matching branchesand leaving only trusted automation/users - Keep
Allow force pushesandAllow deletionsdisabled
- The default bind mode is
lan. Ensure your host firewall and network policy are appropriate. - Use a strong, private
OPENCLAW_GATEWAY_TOKENfor non-local environments. - Keep mounted config directories private because they contain authentication token data.
This project is licensed under the MIT License. See LICENSE for details.