Skip to main content

Overview

OpenClaw is a self-hosted, always-on AI assistant: one Gateway that connects the model of your choice to your chat apps (Telegram, Slack, Discord, WhatsApp) and gives it tools, including a shell, files, a headless browser, scheduled jobs, memory and MCP servers, with a built-in browser Control UI. This template runs the Gateway as a single stateful replica with all of its state on a snapshotted volume. You bring the model key: Anthropic, OpenAI, or any OpenAI-compatible endpoint. The install is private by default. You reach the Control UI over cpln port-forward for setup, and turn on public access (or a custom domain) for daily use. The Control Plane MCP server is pre-registered, so the assistant can manage your Control Plane resources once someone signs in.
This template does not create a GVC. You must deploy it into an existing GVC that you already have, and that GVC should have a single location (see Prerequisites).

What Gets Created

The Gateway runs as one replica by design: OpenClaw supports a single Gateway per state directory. For a separate assistant per person or team, install another release.

Prerequisites

1

Create the dictionary secret

One dictionary secret holds the Gateway token, your model key, and the bot tokens of any chat channels you enable. Secrets are org-level, so no GVC flag is involved.
Set secret.name to this name. Channel keys can be added later; see Connecting Chat Channels.
2

Use a single-location GVC

A workload runs in every location of its GVC, so a multi-location GVC would run one assistant per location, each competing for the same bot tokens. Install into a GVC with one location.
3

Optional: a custom domain

Only when you want the Control UI on your own hostname. You create the domain yourself; the template never creates one. See Custom Domain.
Create the secret before installing, or the deployment wedges silently. A secret.name that points at a secret which does not exist installs “successfully” and then never starts. The container never runs, so cpln logs returns zero lines. The one place the reason appears is status.versions[].message:
Use get-deployments; plain cpln workload get has no versions key. Once the secret exists the workload recovers on its own after a wait, or run cpln workload force-redeployment RELEASE_NAME-openclaw --gvc GVC_NAME to skip it. The same applies to a chat channel enabled before its token key is in the secret.
Backups need nothing further: they are platform-managed volume snapshots with no bucket or cloud account involved. See Backing Up.

Installation

Install from the marketplace registry with the one required value, the name of the secret you created:
The default model is Anthropic’s claude-opus-5-5; add --set model.provider=... and --set model.name=... for another (see Model). Then follow the instructions for your preferred method:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Configuration

One subsection per values area. Each block shows the shipped defaults. Values seed OpenClaw’s config on the first boot. Afterwards, a helm upgrade re-applies model, channels, httpApi and cplnMcp only when those values change; settings you change in the Control UI persist otherwise. A value change restarts the Gateway, so the assistant is briefly offline. An upgrade only carries the values you pass it, so always repeat the ones you installed with:

Secret

  • secret.name — Name of the dictionary secret from Prerequisites. The keys it must contain depend on the channels you enable. A gateway-token shorter than 24 characters stops the Gateway at start (see Troubleshooting).

Model

  • model.provider — anthropic, openai, or custom for any OpenAI-compatible endpoint, such as OpenRouter or a LiteLLM gateway. The key is always llm-api-key in your secret.
  • model.name — The bare model ID from your provider. The chart adds the provider prefix itself and refuses a name containing /.
  • model.baseUrl — Required with custom and refused with the other providers. Usually ends in /v1.
After changing model.provider on a running install, rebuild the memory index; see Important Notes.

Channels

All four channels connect outbound, so they work on a private install with no public URL. Add a channel’s token keys to your secret before enabling it. Bot creation, pairing and the WhatsApp link are in Connecting Chat Channels.

Browser Tool and HTTP API

  • browser.enabled — Run the image variant with headless Chromium, so the agent can open and read web pages. With it off, resources.maxMemory: 2Gi is enough.
  • httpApi.enabled — Serve an OpenAI-compatible API at /v1/chat/completions and /v1/responses on port 18789, authenticated with the gateway token. See Connecting for the headers each caller needs.

Control Plane MCP

  • cplnMcp.enabled — Register the Control Plane MCP server as cpln. Nobody is signed in at install, so it grants nothing until someone signs in.
  • cplnMcp.signIn — shared (default): you sign in once from a terminal and every chat acts with your Control Plane account. per-requester: each chat user signs in with their own account from a chat channel. per-requester requires publicAccess.enabled: true; the chart refuses to render it otherwise.
The two modes sign in differently; see Connecting Control Plane Tools (MCP).

Resources and Storage

  • image.tag — The exact OpenClaw release. Take a snapshot before changing it; see Backing Up.
  • resources — CPU and memory reserved (min*) and the limits (max*). On a stateful workload maxCpu may be at most 4 times minCpu; the chart refuses a wider ratio.
  • volumeset.capacity — Initial volume size in GiB (minimum 10). With volumeset.autoscaling.enabled the volume grows by scalingFactor when free space drops below minFreePercentage, up to maxCapacity.
  • backup.* — Scheduled volume snapshots. schedule is a 5-field cron in UTC with a single fixed minute, because the platform runs snapshots at most hourly; the chart refuses a schedule such as */10 * * * *.

Access

  • publicAccess.enabled — Serve the Control UI on the public canonical HTTPS endpoint. While it is off, requests from the internet are refused and the UI is reachable over cpln port-forward only.
  • publicAccess.origin — The https origin of a custom domain (no path, no trailing slash). Requires publicAccess.enabled: true. See Custom Domain.
  • internalAccess.type — Which workloads may call port 18789 from inside Control Plane:
With publicAccess.enabled, an agent with a shell sits on the internet behind one token. Keep the gateway token long and secret, keep DM pairing on, and vet third-party skills before installing them.
An access change restarts the Gateway and takes from about 30 seconds to 10 minutes to take effect. Re-test before concluding that a value did not work.

Connecting

Verify the Gateway is up from your own machine. In one terminal:
In another, the endpoint the readiness check uses should answer 200:
The port-forward is fine for setup, but browser tabs through the tunnel can freeze. Use the public endpoint or a custom domain day to day.

First Run

On the port-forward, sign in to http://localhost:18789 with the gateway token; that browser is approved automatically. On the public endpoint or a custom domain each new browser needs a one-time approval:
1

Read the gateway token

Copy the value of gateway-token.
2

Sign in

Open the Control UI and paste the token. The page then shows Approve this browser with a request ID.
3

Approve the browser

List pending requests if you need to check the ID, then approve it:
The waiting page connects by itself within seconds.
Approvals live on the volume: they survive restarts, upgrades and a token rotation, but a reinstall asks again.

Custom Domain

Create a Control Plane domain routed to port 18789 of RELEASE_NAME-openclaw, then set publicAccess.origin to its https origin, for example https://assistant.example.com. The canonical endpoint is then refused by the origin check. After an uninstall and reinstall, delete and recreate the domain: it does not rebind to the new workload by itself.

Connecting Chat Channels

Telegram, Slack and Discord connect outbound, so they work on a private install too. For each one:
1

Create the bot and add its token to your secret

Create the bot (table below), then add its key or keys to your secret before enabling the channel. Export the secret, add the key under data, and re-apply it:
A channel enabled without its key wedges the deployment silently (see Prerequisites). Delete secret.yaml afterwards; it holds your keys in plain text.
2

Enable the channel

Set channels.<name>.enabled: true and run cpln helm upgrade with your other values. The Gateway restarts (about 2 minutes) and connects. Check with channels status (see Restarting the Gateway or a Channel); it should read running, connected. A token changed later needs cpln workload force-redeployment.
3

DM the bot and approve the pairing code

The bot replies with a pairing code. Approve it, using slack or discord instead of telegram as needed:
DM again, and the reply comes from the model. The first approved sender becomes the assistant’s owner. Every new contact gets a pairing code and is ignored until you approve it.
Only DMs are answered at first. Messages in a Slack channel or a Discord server are ignored until that channel or server is allowlisted. See the upstream Slack access control and Discord setup docs. WhatsApp needs no token. With channels.whatsapp.enabled: true, open the Control UI over the public endpoint → Settings → Channels → WhatsApp → Show QR, then on the phone go to Settings → Linked devices → Link a device. The image does not refresh, so click Show QR again after about 60 seconds. Do not abandon a half-done setup tab; it blocks a new one for about 5 minutes. On a private install, print the QR in a terminal instead:
Use a dedicated number: this is unofficial WhatsApp Web, and accounts can be banned.

Connecting Control Plane Tools (MCP)

With cplnMcp.enabled the Control Plane MCP server is registered as cpln; nobody is signed in at install. Pick the mode by where you want to sign in. The two are not interchangeable: Shared: sign in from a terminal. Asking the bot to sign in from a chat does not work in this mode: it hands you a link whose callback goes to 127.0.0.1:8989 and fails with connection refused. Instead run these two commands in two terminals; the tunnel carries the browser’s return to the Gateway:
Open the printed URL and approve. The login command reports success, and you can stop the tunnel. Everyone who can message the assistant then acts with your Control Plane permissions. The Control UI’s MCP page shows only this command; its Sign in button does not work through cpln port-forward. Per-requester: sign in from a chat. This needs publicAccess.enabled, because the link returns to the Gateway’s public address. Connect a chat channel first, then in a DM ask the assistant to use Control Plane (for example “list my GVCs”). It replies with a sign-in link; open it, approve with your own Control Plane account, and ask again. Each chat identity signs in once, so your Slack and Discord accounts each get their own link. Links are single-use and whoever opens one connects their account, so do not request them in group chats. The Control UI chat cannot sign in in this mode. To switch an existing install, set cplnMcp.signIn and run cpln helm upgrade, then ask for a new link; links issued in the old mode do not work. Check either mode:
Shared mode reads oauth: authorized; per-requester reads connected principals: N.

Operations

Backing Up

Backups are scheduled volume snapshots managed by the platform; no cloud account or bucket is required. By default the volume is snapshotted daily at 03:00 UTC and each snapshot is kept for 7 days (backup.schedule, backup.retention). Snapshots contain your WhatsApp session and MCP sign-in tokens, so treat them as secrets. List the snapshots of the release’s volume set:
Take a manual snapshot before changing image.tag. A new OpenClaw release runs one-way state migrations, so an older release cannot read the migrated state. Read your GVC’s location from spec.staticPlacement.locationLinks with cpln gvc get GVC_NAME -o yaml, then:

Restoring a Backup

A snapshot restores in place, onto the release’s own volume set; the platform then replaces the replica and the Gateway boots on the restored state. There is no path to restore into a different or reinstalled release.
Restoring reverts the volume to the snapshot, so everything written after it is gone, including newer pairings, channel links and MCP sign-ins. The assistant is offline while the replica is replaced.
1

Find the snapshot

List snapshots and note the name and location of the one you want:
2

Restore it

The release has one replica, so the volume index is 0:
3

Verify

Wait for the workload to report ready with cpln workload get-deployments RELEASE_NAME-openclaw --gvc GVC_NAME -o yaml, then sign in and confirm your sessions and settings. To roll back an image upgrade, restore the snapshot you took before it and upgrade again with the previous image.tag.

Restarting the Gateway or a Channel

Restart the Gateway in place. It takes about 15 seconds after any running agent turn finishes, and keeps serving while it waits; channels and open Control UI tabs reconnect:
Restart one channel, then confirm with channels status. The start call returns "started": true even if the channel fails moments later, for example with a bad token, so the status is what to read:
Use cpln workload force-redeployment RELEASE_NAME-openclaw --gvc GVC_NAME only as a last resort (it takes the assistant offline for about 4 minutes), or after changing a key in your secret, because a new key reaches only a new replica. Pairings, the WhatsApp link and MCP sign-ins survive it.

Scaling and Availability

The Gateway runs as exactly one replica, with no replicas value: OpenClaw supports a single Gateway per state directory, and each bot token can hold only one connection. A restart, a value change or a platform reschedule is therefore a short full outage, after which the Gateway comes back on the same volume with its state intact. Scale vertically with resources, keeping maxCpu at most 4 times minCpu. For more isolation, such as one assistant per team, install another release with its own secret and bot tokens.

Troubleshooting

Symptom: cpln helm install reported success, the workload has no ready replica, and cpln logs returns zero lines. cpln workload get-deployments RELEASE_NAME-openclaw --gvc GVC_NAME -o yaml shows under status.versions[].message:
Cause: secret.name names a secret that does not exist, or a channel was enabled before its key was in the secret.Fix: Create the secret, or add the missing key, as in Prerequisites. The workload recovers on its own after a wait, or run cpln workload force-redeployment RELEASE_NAME-openclaw --gvc GVC_NAME to skip it.
Symptom: The container restarts repeatedly and the logs show:
Cause: The gateway-token key is too short.Fix: Replace it with a longer value (export, edit and re-apply the secret as in Connecting Chat Channels), then run cpln workload force-redeployment RELEASE_NAME-openclaw --gvc GVC_NAME. A running replica does not pick up a changed key by itself.
Symptom: A call from another workload to /v1/chat/completions fails with 403 proxy_attribution_required even with the correct bearer token.Cause: In-GVC callers must send an X-Forwarded-For header with a non-loopback address. A missing header and 127.0.0.1 are refused alike.Fix: Add a header such as X-Forwarded-For: 10.0.0.1 alongside Authorization: Bearer.
Symptom: With model.provider: openai, replies fail with You’ve reached your Codex subscription usage limit.Cause: Most likely your OpenAI API account is out of credit or quota. OpenClaw runs openai/* models on its Codex runtime, which words billing errors this way.Fix: Check the credit and quota of the OpenAI account behind llm-api-key. To see the provider’s real error (for example credit_balance_exhausted), pin the model to the openclaw runtime with agents.defaults.models["openai/MODEL"].agentRuntime.id.
Symptom: The bot answers DMs but not messages in a Slack channel (the Gateway logs channel-not-allowed) or a Discord server (the Control UI shows Discord as needs attention). Or a Slack DM shows Sending messages to this app has been turned off.Cause: Only DMs are answered until a channel or server is allowlisted. The Slack DM message is an app setting.Fix: Allowlist the channel or server as described in the upstream Slack and Discord docs. For the Slack DM message, open App Home → Messages Tab, allow messages, then reload Slack.
Symptom: After switching model.provider, the assistant cannot recall anything it saved.Cause: The memory index was built for the previous provider.Fix: Rebuild it:
Symptom: The domain reads ready, but the Control UI on it is unreachable after you uninstalled and reinstalled the release.Cause: A domain does not rebind to a new workload by itself.Fix: Delete the domain and create it again with the same route to port 18789 of RELEASE_NAME-openclaw.
Symptom: You upgraded with publicAccess.enabled: true and the canonical endpoint still refuses requests.Cause: An access change restarts the Gateway and takes from about 30 seconds to 10 minutes to take effect.Fix: Wait and re-test. If it persists, confirm the upgrade carried publicAccess.enabled=true with cpln workload get RELEASE_NAME-openclaw --gvc GVC_NAME -o yaml.

Important Notes

  • Approve only chat contacts you would trust as admins. An approved contact can have the agent run shell commands in the container without asking, and those commands can read the gateway token and the workload’s Control Plane token; per-requester MCP sign-in does not change that. To hold each command for your approval, run node openclaw.mjs config set tools.exec.ask always through cpln workload exec as in Connecting.
  • With publicAccess.enabled, an agent with a shell is on the internet behind one token. Keep the gateway token long and secret, keep DM pairing on, and vet third-party skills.
  • Every new chat contact gets a pairing code and is ignored until you approve it with pairing approve CHANNEL CODE.
  • Take a volume snapshot before changing image.tag. Image upgrades run one-way state migrations, so a downgrade cannot read migrated state.
  • The assistant spends model tokens on its own. OpenClaw runs a heartbeat turn every 30 minutes by default; change or turn it off in the Control UI.
  • After changing model.provider, rebuild the memory index with node openclaw.mjs memory status --index --agent main; until then memory search returns nothing.
  • The Control UI microphone only dictates until you choose a voice provider. For spoken conversation (Talk), run node openclaw.mjs config set talk.realtime.provider openai.
  • To run sessions on another machine (a laptop, a VM), pair it as a session host using the public endpoint. With model.provider: openai it also needs the Codex placement steps, or it stays greyed out in the picker.
  • A changed key in your secret needs a forced redeployment to reach the Gateway, which takes the assistant offline for about 4 minutes.
  • A custom domain must be deleted and recreated after a reinstall; it does not rebind to the new workload.
  • Uninstall deletes the volume and all state, including the WhatsApp link, MCP sign-ins, sessions and workspace, with no tested way to restore it into a new install. Copy out anything you need first. Your own prerequisite secret is not deleted.

External References

OpenClaw on GitHub

Upstream source repository

OpenClaw Documentation

Official documentation

Control UI

Using the browser Control UI

Channels

Chat channel setup and access control

OpenClaw Template

View the source files, default values, and chart definition