Skip to content

Repository files navigation

openclaw-docker

Containerized OpenClaw Gateway setup with Docker and Docker Compose.

This repository provides:

  • A production-oriented Dockerfile based on Ubuntu 24.04
  • Automatic OpenClaw installation from the official installer
  • A startup entrypoint that initializes openclaw.json on first run
  • Persistent config/workspace volumes for local development and daily use
  • A ready-to-run docker-compose.yml service definition

Repository Layout

  • Dockerfile: image build and gateway entrypoint script
  • docker-compose.yml: local runtime configuration
  • scripts/build-image.sh: local image build/tag helper for Compose
  • scripts/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-repo
  • OPENCLAW_UPSTREAM_VERSION: tracked upstream major version used by sync workflow
  • .github/workflows/bats-tests.yml: runs bats unit tests for release and workflow guardrails
  • tests/create-tag.bats: unit tests for release tag script
  • tests/build-image.bats: unit tests for local image build/tag script
  • tests/docker-compose.bats: unit tests for compose image-only guardrails
  • tests/tag-build-workflow.bats: unit tests for Docker image publish workflow guardrails
  • tests/sync-upstream-major-workflow.bats: unit tests for upstream version sync workflow guardrails
  • LICENSE: MIT license

Prerequisites

  • Docker Engine with Docker Compose v2, or Podman with podman compose / podman-compose
  • Network access during image build (for package install and OpenClaw installer)

Quick Start

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.

Using Docker (by default)

  1. Build local image for the default tag (latest):
./scripts/build-image.sh
  1. Start service:
docker compose up -d
# Or use the wrapper script: ./scripts/compose.sh up -d

This mounts the full /home/node tree to the host through OPENCLAW_HOME_DIR.

  1. Check container status:
docker compose ps
# Or: ./scripts/compose.sh ps

Inside the container, openclaw gateway status reports a healthy foreground runtime as Runtime: running (container foreground).

  1. Check gateway health endpoint:
curl http://127.0.0.1:18789/healthz

If you override OPENCLAW_GATEWAY_PORT, Compose now publishes the same port on both the host and the container.

  1. 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
  1. Stop service:
docker compose down
# Or: ./scripts/compose.sh down

Using Podman Instead of Docker

Click to expand instructions

Podman supports rootless containers, so regular users can build, run, and manage them without sudo. To use Podman:

  1. 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-compose

Make sure ~/.local/bin is in your PATH if you install podman-compose with --user.

  1. Set the CONTAINER_RUNTIME environment variable for your current shell:
export CONTAINER_RUNTIME=podman
  1. 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 down

The 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 -d

First-Time Onboarding in Container

After the first startup, enter the container and run onboarding:

docker compose exec openclaw-gateway bash
# Or: ./scripts/compose.sh exec openclaw-gateway bash
openclaw onboard

Notes 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 onboard again.
  • Existing onboarding progress is reused from persisted config, so you only need to complete the remaining steps.

Persistence and Default Paths

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 -d

This 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

Migrating from Legacy Split Mounts (v2026.4.9 and Earlier)

Versions v2026.4.9 and earlier used a split layout like this:

  • OPENCLAW_CONFIG_DIR -> /home/node/.openclaw
  • OPENCLAW_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 -d

If you used custom legacy host paths, replace ./openclaw-data above with your previous config and workspace directories before starting the current Compose setup.

First-Run Config Initialization

If /home/node/.openclaw/openclaw.json does not exist, the entrypoint generates it with:

  • gateway.mode from OPENCLAW_INIT_GATEWAY_MODE (default: local)
  • gateway.bind from OPENCLAW_GATEWAY_BIND (default: lan)
  • gateway.auth.token from OPENCLAW_GATEWAY_TOKEN, or auto-generated when empty
  • gateway.controlUi.allowedOrigins from OPENCLAW_INIT_CONTROL_UI_ALLOWED_ORIGINS, or http://127.0.0.1:<port> by default

If a token is generated automatically, it is persisted in openclaw.json and reused on later starts.

Main Environment Variables

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

Getting the Current Gateway Token

If you did not set OPENCLAW_GATEWAY_TOKEN manually, inspect the generated config:

jq -r '.gateway.auth.token' ./openclaw-home/.openclaw/openclaw.json

If jq is not installed:

grep -n '"token"' ./openclaw-home/.openclaw/openclaw.json

Local Image Build and Run

Build image with helper script:

./scripts/build-image.sh --tag 2026.3.11.2

Or 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 -d

Run 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 gateway

Release Tag and Image Automation

Create a local release tag

Use the script from repository root:

./scripts/create-tag.sh

Optional: force a specific major version:

./scripts/create-tag.sh --major 2026.3.11

Script behavior:

  • Always runs git fetch --tags origin first
  • Major version source is openclaw/openclaw release tags:
    • vX.Y.Z
    • vX.Y.Z-N (treated as major vX.Y.Z)
    • Pre-release tags like -beta.* are ignored
  • If --major is provided, it must exist in openclaw/openclaw
  • If local repo does not have vX.Y.Z, it creates vX.Y.Z
  • If local repo already has vX.Y.Z, it creates the next patch tag vX.Y.Z.N (auto increment)
  • It only creates local tag; push is manual

Push manually when ready:

git push origin <tag>

Auto build on tag push

Workflow: .github/workflows/tag-build.yml

  • Trigger: git push of tag matching v*
  • 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>-amd64
      • tenfyzhong/openclaw:<git-tag-without-v>-arm64
    • Publishes multi-arch manifest tags:
      • tenfyzhong/openclaw:<git-tag-without-v>
      • tenfyzhong/openclaw:latest
  • 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 run command example
  • Build arg OPENCLAW_VERSION always uses major base (X.Y.Z)
    • Example: git tag v2026.3.11.2 builds with OPENCLAW_VERSION=2026.3.11
    • Both tags are published as multi-arch manifest lists on Docker Hub

Manual multi-arch push to Docker Hub

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.

Manual sync entry (GitHub Actions)

Workflow: .github/workflows/sync-upstream-major.yml

Run manually from GitHub:

  1. Open repository Actions
  2. Select Sync Latest Upstream Major Tag
  3. Click Run workflow

Behavior:

  • Fetches latest stable major tag from openclaw/openclaw
  • Uses OPENCLAW_UPSTREAM_VERSION to 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
  • If version file is already current, it only checks whether the tag exists
  • Pushed tag triggers tag-build.yml to build/push Docker image

Required GitHub Secrets

Configure repository secrets in Settings -> Secrets and variables -> Actions:

  • DOCKERHUB_USERNAME: Docker Hub username
  • DOCKERHUB_TOKEN: Docker Hub access token (for docker/login-action)
  • RELEASE_PUSH_TOKEN: GitHub token used by manual sync workflow to push tags

How to create RELEASE_PUSH_TOKEN

Recommended: Fine-grained personal access token.

  1. GitHub avatar -> Settings
  2. Developer settings -> Personal access tokens -> Fine-grained tokens
  3. Click Generate new token
  4. Set token name and expiration
  5. Repository access: select only this repository
  6. Repository permissions:
    • Contents: Read and write
    • Metadata: Read-only (default)
  7. Generate token and copy it immediately
  8. Go back to repository Settings -> Secrets and variables -> Actions
  9. New repository secret
  10. Name: RELEASE_PUSH_TOKEN
  11. Value: the generated token

After saving, rerun Sync Latest Upstream Major Tag workflow.

Run release script tests

bats tests/*.bats

CI workflow Bats Unit Tests runs automatically on:

  • All pull requests targeting main
  • Pushes to main

Protect main branch on GitHub

To require CI success before merge and block direct pushes:

  1. Go to repository Settings -> Branches -> Add branch protection rule
  2. Set Branch name pattern to main
  3. Enable Require a pull request before merging
  4. Enable Require status checks to pass before merging
  5. Select status check Bats Unit Tests / bats
  6. Enable Require branches to be up to date before merging (recommended)
  7. Enable Include administrators (recommended)
  8. Disable direct push by enabling Restrict who can push to matching branches and leaving only trusted automation/users
  9. Keep Allow force pushes and Allow deletions disabled

Security Notes

  • The default bind mode is lan. Ensure your host firewall and network policy are appropriate.
  • Use a strong, private OPENCLAW_GATEWAY_TOKEN for non-local environments.
  • Keep mounted config directories private because they contain authentication token data.

License

This project is licensed under the MIT License. See LICENSE for details.

About

Containerized OpenClaw Gateway setup with Docker and Docker Compose

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages