Files
openclaw/scripts/cloudflare/README.md
T
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

165 lines
8.2 KiB
Markdown

# 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](#operational-constraints) before using it with real credentials or relying on it for recovery.
## Architecture
```text
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:
```bash
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`](https://hub.docker.com/r/openclaw/openclaw) Docker Hub repository. Replace `<official-openclaw-image-digest>` in `Dockerfile`, then build and push the derived image:
```bash
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`:
```bash
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:
```bash
npm run check
npm run deploy
```
Immediately add the R2 and Gateway secrets. `wrangler secret put` prompts without writing the value to shell history:
```bash
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:
```bash
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:
```bash
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:
```bash
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`](https://docs.openclaw.ai/install/backups#full-archives) 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:
```bash
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