OpenClaw EnterpriseDOCSGitHub

Render installation profiles

For a custom production installation, choose a profile and render Helm values and Installation YAML from JSON. For OpenShell, use Kubernetes setup or EKS setup and its first-Agent session.

Profiles render configuration; they do not create clusters, Secrets, databases, DNS, certificates, hosted plugin credentials, ChatGPT service accounts, repository registries, or Slack consumers. Prepare routing prerequisites before installation; rendering does not verify cluster readiness.

Choose a profile

Use openclaw for embedded OpenClaw Agents with the bundled OpenClaw PluginDriver. Operators select plugins manually or through the API. Embedded OpenClaw cannot enable external Slack channels; the Standard OpenClaw preset must leave channels disabled.

Use codex for dedicated Codex Agents with the bundled Codex PluginDriver. By default, hosted plugin discovery and Codex runtime authentication both use a codex_pat token that you supply later: add a same-Namespace Secret or enter the PAT during Agent creation. The renderer does not create the token or make catalog discovery ready.

Both profiles enable:

Both profiles set a 100m CPU request and four-core ("4") burst limit for Gateway Pods (embedded or dedicated), Harness Pods, and the tenant namespace container default. An embedded OpenClaw Gateway's startup model probe runs a full agent turn (~16 CPU-seconds on about one core). Readiness takes 24–30 seconds at one core or 51–72 seconds at 500m. Only the request reserves node capacity. Busy runtimes sharing a node can each consume four cores, and a limits.cpu namespace quota counts the full limit.

Memory requests reserve measured usage between turns. Limits cover measured peaks:

Pod Request Limit Measured
Gateway (embedded or dedicated) 2560Mi 4Gi embedded: 1.4 GiB at ready, 2.1 to 2.2 GiB between turns, peaks of 2.2 to 2.4 GiB on the first turn (no swap); dedicated Codex: 1.75 to 1.8 GiB between turns, peaks of 1.95 to 2 GiB on a short first turn, but native admin chat was OOM-killed at 2Gi on a coding turn
Codex Harness 768Mi 6Gi 0.45 to 0.57 GiB between turns, 1 GiB running a test suite, 1.9 GiB running tsc; lint, tsc and tests together were OOM-killed at 2Gi and reached a 4Gi limit
Container default 128Mi 2Gi containers that set no resources

Plan node memory per Agent: an embedded OpenClaw Agent reserves 2560Mi, and a dedicated Codex Agent reserves 3328Mi: 2560Mi for its Gateway on the runtime.gatewayNodeSelector pool and 768Mi for its Harness on the runtime.nodeSelector pool. With initial workspace files, the Harness Pod also runs a setup init container that requests 2560Mi. Kubernetes reserves the larger init or app request, so the Agent reserves 5120Mi. A Harness runs the Agent's shell commands, so builds and test suites in large repositories can need more than 6Gi; raise resources.agent.limits.memory in the Installation for such workloads. Limits reserve no node memory, so Harnesses building at the same time on one node can together exhaust it, and the kubelet then evicts Pods using more than their requests first; size agents nodes for the turns you expect to run concurrently.

Denying the 11 bundled plugins an OpenAI-only Agent does not use (plugins.deny) saved only about 50 MiB. Profile input cannot change these values; for others, write the Installation from the production example (see Images and resources). An existing Installation keeps its values on upgrade; adopted values apply to each Agent at its next deployment. Before adopting them, check each agents node's free memory: during a rollout the old and new Harness Pods of an Agent can run together on one node, because they share its workspace volume.

Seeding both Presets does not change the profile's PluginDriver. An Agent created from the other profile's Preset still needs a compatible driver, runtime, harness mode, credentials, and channel support.

To seed additional Presets, add "presets": { "files": ["/app/deploy/presets/swe-preset.json"] } to the input JSON and rerender. This example adds SWE Agent alongside the standard Presets. Both controller processes must be able to read the files at startup; the renderer validates the list but does not read container files. See Preset initialization for path resolution and startup validation.

Repository support is optional. When enabled, the renderer wires the broker container, GitHub Backend, Repo Driver, and Compute peer. The repository registry needs Namespace IDs that only exist after the first bootstrap, so most installs render with repositories disabled, bootstrap the platform, create the registry, then rerender with repository.enabled: true.

Prepare inputs

Create a JSON file outside the repository or under an ignored local output directory. The renderer rejects any field it does not consume. controlPlane.releaseName must satisfy Helm's lowercase release-name syntax and be at most 53 characters. Image references in controlPlane.controllerImage, runtime.image, and enabled repository.image must use literal sha256 and 64 lowercase hexadecimal characters. All three must also be references Kubernetes can pull, as the chart and Compute check: only the registry host and tag may use uppercase, and an IPv6 registry host is allowed. controlPlane.adminEmail must be an administrator email the bootstrap Job accepts after trim and lowercase: one @ and a dotted domain.

json
{  "controlPlane": {    "releaseName": "oce",    "namespace": "openclaw-system",    "clusterName": "production-west",    "controllerImage": "registry.example/openclaw-enterprise/controller@sha256:<64-hex>",    "authBaseUrl": "https://console.example.internal",    "adminEmail": "admin@example.invalid",    "bootstrapPasswordClaimName": "occ-bootstrap-admin-password",    "apiClients": [{ "namespace": "operator-tools", "podLabels": { "app": "operator" } }],    "databaseCidrs": ["10.45.0.12/32"],    "clusterCidrs": ["10.43.0.1/32"],    "dns": { "namespace": "kube-system", "podLabels": { "k8s-app": "kube-dns" } },    "gatewayClassName": "eg",    "gatewayApiKeySecretName": "occ-private-gateway-key",    "agentNativeAdminDomain": "agents.example.internal",    "gatewayTrustedProxyCidrs": ["10.46.0.0/24"],    "pluginStatusProxySourceCidrs": ["10.47.0.10/32"],    "nodeSelector": { "oce-role": "control" }  },  "runtime": {    "image": "registry.example/openclaw-enterprise/runtime@sha256:<64-hex>",    "gatewayStorageClassName": "occ-gateway-rwo",    "nodeSelector": { "oce-role": "agents" },    "gatewayNodeSelector": { "oce-role": "control" },    "transportSecretPrefix": "openclaw-agent-transport"  },  "channels": {    "managedSlackProxy": true  }}

controlPlane.gatewayClassName, controlPlane.gatewayApiKeySecretName and controlPlane.databaseCa.secretName must be Kubernetes resource names of at most 253 characters: the existing GatewayClass that Envoy Gateway serves, the Secret that holds the occ key, and the database CA Secret.

For the codex profile, add the reviewed Codex seccomp profile to the base input:

json
{  "runtime": {    "codexSeccompProfile": "openclaw/codex-0.164.0-<profile-sha256>.json"  }}

Both profiles accept runtime.modelProvider for API-key inference. Preflight validates its endpoint.

To have OCE issue managed ChatGPT service-account runtime credentials, add the optional managed ServiceAccount binding. Do this only after you qualify the ChatGPT Backend admin credential path:

json
{  "codex": {    "managedServiceAccounts": {      "workspaceId": "11111111-1111-4111-8111-111111111111",      "adminSecretName": "occ-chatgpt-admin",      "adminSecretKey": "admin-key",      "providerCidr": "198.51.100.30/32"    }  }}

adminSecretName must be a lowercase DNS-subdomain Kubernetes resource name of at most 253 characters. adminSecretKey accepts letters, digits, -, _ and ., up to 253 characters, but it cannot be . or start with ..; omit it to keep the admin-key default.

workspaceId must be a UUID the controller accepts at startup: version digit 1–8 and variant 8, 9, a, or b, as in the example. Any other spelling, including a nil UUID, is a preflight error. Managed issuance is separate from the default codex_pat path. The rendered Backend and ServiceAccount Driver wiring does not prove that live service-account creation works. Optional credentialTtlSeconds must be an integer from 1 through 2592000, the lifetime the API accepts; omit it to use 2592000.

controlPlane.loggingCollector.enabled turns on the log Collector, which needs an exporter: a /32 cidr or paired namespaceLabels and podLabels, and an optional port (default 443).

To show Installation administrators an external Observability console link, set controlPlane.observabilityUrl. The renderer writes it as observability.url and rejects URLs the controller would reject at startup, including any @ before the host and a tab, newline, or a backslash before the query.

Hosted discovery egress

With hosted codex-plugin, omitted codex.modelDiscoveryCidrs resolves auth.openai.com and chatgpt.com from the renderer host into Helm api.modelDiscoveryCidrs. This grants only release API Pods TCP 443 to those IPv4 /32 hosts.

Supplied lists stay unchanged without DNS queries; [] requires an external HTTPS policy for both hosts. DNS failure leaves both YAML files absent.

Verify API Pod DNS/NAT destinations and Load plugins with an authorized PAT. Rerender and upgrade Helm after address changes; this initial snapshot does not refresh itself.

External sign-in and trusted proxies

Activation of Google or OIDC sign-in is one-way, so keep the selected provider in later renders. controlPlane.google: {} uses the chart's Secret defaults. controlPlane.oidc requires issuer, authorizationUrl, tokenUrl and jwksUrl; see OIDC setup. Both require an HTTPS Console origin and the existing recoveryUserId. Google disables native access; OIDC supports it. To use host-only exchange, set controlPlane.agentNativeAdminSessionMode: "host-only-exchange" and use same-site HTTPS Console and Agent domains.

Use controlPlane.oidc.enrollment for the enrollment policy, including admission, initial Role and Group selection. controlPlane.github is rejected; existing installations must follow the upgrade instructions. Follow external sign-in activation before enabling the provider. Confirm existing users' attachments before selecting passwordSignIn: "recovery-only".

Behind an HTTP proxy, set trustedProxy using the supported presets. Without it, preflight warns because the API cannot identify individual clients through a shared proxy address. A source-preserving load balancer needs no preset.

Client-ID and client-secret Secret keys must differ; an omitted key counts as the chart default (client-id or client-secret). As the chart requires, each credential Secret is dedicated: a sign-in secretName (default occ-google-login or occ-oidc-login), gatewayApiKeySecretName, databaseCa.secretName (which may equal repository.publicCaSecretName), the ChatGPT admin Secret (with codex.managedServiceAccounts) and each repository Secret must differ from one another, from the chart's occ-installation-startup, occ-database and occ-auth Secrets, from the Gateway TLS (<release>-agent-gateways-tls for short release names) and root CA (occ-gateway-<hash>-root) Secrets the chart generates, and, with controlPlane.loggingCollector.enabled, from occ-otel-collector-config and occ-otel-collector-exporter.

google and oidc accept secretName, clientIdKey, clientSecretKey and egressCidrs. OIDC also accepts tokenAuth, displayName and enrollment. trustedProxy accepts clientAddressHeader, required for the generic preset. Preflight refuses x-occ-client-ip, cookie, forwarded, authorization, host, origin, and x-api-key; use a header that carries plain client addresses, such as x-forwarded-for or x-real-ip. Each google.allowedDomains entry must be a lowercase DNS name of at most 253 characters, such as example.com, and its last label must start with a letter. The chart and the API also refuse example.123 and any longer name.

If you opt in to repositories, add the broker inputs:

json
{  "repository": {    "enabled": true,    "image": "registry.example/openclaw-enterprise/repository-credentials@sha256:<64-hex>",    "backendId": "github-primary",    "registryConfigMapName": "occ-repository-registry-v1",    "serviceConfigSecretName": "occ-repository-service-config",    "appKeySecretName": "occ-repository-app-key",    "tlsSecretName": "occ-repository-tls",    "publicCaSecretName": "occ-repository-public-ca",    "serviceName": "git",    "upstreamCidrs": ["198.51.100.0/24"]  }}

The repository registry ConfigMap and all four repository Secret names must be Kubernetes DNS-subdomain resource names of at most 253 characters.

serviceName is optional. When omitted, the renderer leaves it out of values.yaml: a new installation gets the chart's git Service, and a Helm upgrade fails until you set it. When upgrading an installation whose broker Service has another name, set serviceName to that current name so TLS and active repository sessions keep working, then switch it deliberately after sessions drain. A set name must be a DNS-1035 label of at most 63 characters: a lowercase letter, then lowercase letters, digits, or hyphens, ending in a letter or digit. The chart refuses any other spelling too.

Render files

Run the renderer from the repository root with Node.js satisfying engines.node in the release checkout's package.json. With native admin, run pnpm install first: the public suffix check uses the API's list.

sh
node scripts/render-installation-profile.ts \  --profile codex \  --input .build/profile-inputs/codex.json \  --out-dir .build/profile-renders/codex

On success, the output directory contains:

Use a separate input file. Symbolic or hard links to these outputs are also refused without changing existing files. An input.json can remain in the output directory.

After these checks, the renderer clears the three outputs before reading input; other files stay. Invalid settings produce only preflight.json with ok: false and its path in outputs. Unreadable or malformed JSON produces no report. Both failures exit nonzero and leave no YAML outputs.

Use rendered files

Set OCC_INPUT_DIRECTORY to the output directory, then complete the production shell and context setup. That setup reads the cluster kubeconfig from $OCC_INPUT_DIRECTORY/kubeconfig: copy it there with mode 0600 first, or render straight into the protected directory as the runbook's profile branch does. Skip both configuration-generation branches and continue at the shared bootstrap PVC and configuration checks. The runbook covers Secret creation, Helm installation, bootstrap key retrieval, and authenticated API verification.

If controlPlane.databaseCa supplies a CA Secret, omit mountPath to use /etc/openclaw/database-ca, or choose a path distinct from the other active database-client mounts. Preflight rejects collisions with platform mounts and, when enabled, repository credentials or managed ChatGPT account mounts before writing deployment files. See the rendering flow.

controlPlane.nodeSelector, runtime.nodeSelector and runtime.gatewayNodeSelector require Kubernetes label keys and label values that are empty or a label name of at most 63 characters, matching Helm, bootstrap-volume preparation and Pod admission. Preflight rejects invalid placement labels before writing deployment files.

If rendering fails or either YAML file is absent, fix the input and rerender; never copy manual examples into the output directory. Only a successful render keeps values.yaml, installation.yaml, and controlPlane.installationChecksum paired.

runtime.transportSecretPrefix must form a DNS-safe Kubernetes Secret name after - and 12 hex characters are appended, with at most 253 characters in total. Preflight refuses a prefix that the controller would reject at startup.

Required environment checks

The renderer does not check these prerequisites. Verify them before you treat the install as ready:

Search documentation