* 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
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/amd64support - 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>inLITESTREAM_ENDPOINT - change both
LITESTREAM_BUCKETandr2_buckets[].bucket_nameif 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_instancesor 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/*.sqliteand recursive per-agent SQLite databases only. Use a separate, privateopenclaw backup createworkflow for config, credential files, plugins, and workspaces. - RPO:
sync-interval: 1snormally 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
cloudWorkersprovider: 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 bindingsrc/index.ts: routes all HTTP and WebSocket traffic to one named instancesrc/container.ts: Container port, readiness, environment, and sleep policyDockerfile: official OpenClaw image plus pinned Litestream forlinux/amd64entrypoint.sh: R2 LIST restore discovery, containment checks, and restore-then-exec flowlitestream.yml: watched global and per-agent SQLite directory replicas