Files
openclaw/scripts/cloudflare
Peter Steinberger 5fda9d09f0 feat(deploy): add experimental Cloudflare deployment template (#122768)
* feat(deploy): add experimental Cloudflare template

* fix(deploy): keep container SSH debug access opt-in

* fix(deploy): satisfy scripts tsgo lane and model wrangler entrypoint in knip

* fix(deploy): model wrangler-consumed exports and isolated dependency in knip

The Worker default export and Durable Object class are instantiated by
wrangler from wrangler.jsonc, and @cloudflare/containers lives in the
template's isolated package.json — modeled per the deadcode checks' own
guidance rather than root-manifest changes.

* docs(deploy): align SSH bootstrap flow with the disabled-by-default policy
2026-08-12 14:07:48 -07:00
..

OpenClaw on Cloudflare Containers (experimental)

This template runs one OpenClaw installation behind a Cloudflare Worker and one named Durable Object. The Durable Object starts a standard-2 Container from a public, digest-pinned Docker Hub image. Litestream continuously replicates the global and per-agent SQLite databases to R2 through its S3-compatible API.

This is an experimental deployment target. Read Operational constraints before using it with real credentials or relying on it for recovery.

Architecture

HTTP/WebSocket request
        |
        v
Cloudflare Worker
        |
        v
OpenClawContainer Durable Object (one stable name)
        |
        v
OpenClaw + Litestream container :8080
        |
        +--> R2 S3 API (SQLite replicas)

Every HTTP and WebSocket request is forwarded to port 8080. The Container helper checks GET /startupz before admitting traffic. max_instances: 1 and the single Durable Object name are the installation's outer single-writer fence.

Prerequisites

  • A Cloudflare account with Workers, Containers, and R2 available
  • Docker Buildx with linux/amd64 support
  • A public Docker Hub repository for the derived image
  • Node.js and npm
  • Model-provider and channel credentials for the OpenClaw setup you choose

1. Create the R2 bucket and S3 credentials

From this directory:

npm install
npx wrangler login
npx wrangler whoami
npx wrangler r2 bucket create openclaw-backups

In the Cloudflare dashboard, create an R2 API token with object read/write access limited to this bucket. Record its access key ID and secret access key. Do not put either value in this checkout.

Edit wrangler.jsonc:

  • replace <account-id> in LITESTREAM_ENDPOINT
  • change both LITESTREAM_BUCKET and r2_buckets[].bucket_name if you chose another bucket name

The R2 binding is present for Worker-side completeness. Litestream runs inside the Container and cannot consume a Worker binding directly, so it uses R2's S3 endpoint and Worker secrets passed through envVars.

2. Build and publish the image

Choose an immutable, architecture-compatible digest from the official openclaw/openclaw Docker Hub repository. Replace <official-openclaw-image-digest> in Dockerfile, then build and push the derived image:

docker buildx build \
  --platform linux/amd64 \
  --tag docker.io/<docker-hub-user>/openclaw-cloudflare:<version> \
  --push \
  .

Make the derived repository public. Resolve its pushed digest, then replace the containers[].image placeholder in wrangler.jsonc:

docker buildx imagetools inspect docker.io/<docker-hub-user>/openclaw-cloudflare:<version>

Use the resulting immutable docker.io/<docker-hub-user>/openclaw-cloudflare@sha256:<digest> reference. Cloudflare Containers can pull public Docker Hub images, but not GHCR images directly.

3. Deploy and set secrets

The first deploy creates the Worker, Durable Object migration, Container application, and R2 binding:

npm run check
npm run deploy

Immediately add the R2 and Gateway secrets. wrangler secret put prompts without writing the value to shell history:

npx wrangler secret put LITESTREAM_ACCESS_KEY_ID
npx wrangler secret put LITESTREAM_SECRET_ACCESS_KEY
npx wrangler secret put OPENCLAW_GATEWAY_TOKEN

Add the provider and channel variables needed by your installation, for example:

npx wrangler secret put OPENAI_API_KEY
npx wrangler secret put TELEGRAM_BOT_TOKEN

src/container.ts passes the listed optional secret names to the Container. Add another explicit name there before using a different environment-backed provider or channel credential.

4. Bootstrap OpenClaw

Open the deployed Worker URL once to start the named instance. Then find the Container application and instance IDs:

npx wrangler containers list
npx wrangler containers instances <application-id> --json
npx wrangler containers ssh <instance-id>

Inside the Container, run the non-interactive SecretRef bootstrap. This example uses OpenAI and Telegram; select the provider and webhook-capable channel that match your secrets:

cd /app
node openclaw.mjs 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-daemon
node openclaw.mjs channels add --channel telegram --use-env
node openclaw.mjs doctor --json

Keep the exact bootstrap recipe in a private, reproducible runbook. Litestream does not replicate openclaw.json, credential files, installed plugin files, or workspaces.

Scale-to-zero policy

The template defaults OPENCLAW_WEBHOOK_ONLY to false. This keeps the Container alive across idle periods for Discord, Slack Socket Mode, WhatsApp, and every other channel that maintains a socket or polling process.

Set OPENCLAW_WEBHOOK_ONLY to true only when every enabled channel receives traffic through HTTP webhooks. The Container then stops after ten idle minutes and cold-starts on the next request. Because its disk is fresh after sleep, enable this only when an external process can reapply the declarative bootstrap above; Litestream alone restores SQLite, not the config files needed to activate channels.

Operational constraints

  • Experimental: Cloudflare Container lifecycle and rollout behavior can change. Test crash, sleep, rollout, and restore paths with non-production credentials first.
  • Single-writer fence: Cloudflare guarantees one live Durable Object instance for a given name, and all Worker requests use the same name. This is the fence around one Litestream replica. A brief old/new Container overlap during replacement or rollout remains an accepted experimental tradeoff; do not raise max_instances or route around the named object.
  • Ephemeral disk: Every Container restart or sleep starts with a fresh filesystem. The entrypoint lists R2 objects, derives the concrete SQLite restore manifest, restores each database, then starts OpenClaw under Litestream.
  • Partial durability: Litestream covers /home/node/.openclaw/state/*.sqlite and recursive per-agent SQLite databases only. Use a separate, private openclaw backup create workflow for config, credential files, plugins, and workspaces.
  • RPO: sync-interval: 1s normally yields a seconds-scale recovery point, not zero data loss. Abrupt termination can lose writes that were not uploaded yet.
  • Rollback is time travel: Restoring older state can desynchronize ratcheting channel credentials (especially WhatsApp), roll back approvals, and roll back delivery/dedupe state. Relink affected channels and review pending approvals before resuming.
  • WebSocket limit: Cloudflare accepts received WebSocket messages up to 32 MiB. The Worker/Container proxy supports WebSockets; larger individual messages are closed by the platform.
  • Egress identity: outbound traffic comes from shared Cloudflare IP space. Providers that require a fixed source IP need another deployment target or an approved egress design.
  • Not a cloudWorkers provider: this is a hosting template. Operator SSH access is enabled for bootstrap, but the template does not implement OpenClaw's SSH-based cloud-worker provider contract.

Updating

Build a new derived image from a new immutable official OpenClaw digest, push it, replace the derived digest in wrangler.jsonc, and run:

npm run check
npm run deploy

Treat rollbacks like restores: stop traffic where possible, preserve the current state first, and review credentials, approvals, and delivery state before activating older database bytes.

Files

  • wrangler.jsonc: Worker, Durable Object, Container application, and R2 binding
  • src/index.ts: routes all HTTP and WebSocket traffic to one named instance
  • src/container.ts: Container port, readiness, environment, and sleep policy
  • Dockerfile: official OpenClaw image plus pinned Litestream for linux/amd64
  • entrypoint.sh: R2 LIST restore discovery, containment checks, and restore-then-exec flow
  • litestream.yml: watched global and per-agent SQLite directory replicas