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:
- Native admin UI and private gateway routing.
- Standard OpenClaw and Standard Codex Preset seeding.
- Kubernetes Compute, Kubernetes Configuration, Kubernetes Secrets, native IAM, private metrics, digest-pinned images, DNS policy, trusted proxy CIDRs, and plugin-status proxy CIDRs.
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.
{ "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:
{ "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:
{ "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:
{ "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.
node scripts/render-installation-profile.ts \ --profile codex \ --input .build/profile-inputs/codex.json \ --out-dir .build/profile-renders/codexOn success, the output directory contains:
values.yaml: Helm values fordeploy/helm/openclaw-enterprise, includingcontrolPlane.installationChecksumcomputed from the exact renderedinstallation.yamlbytes.installation.yaml: Installation startup YAML for theocc-installation-startupSecret.preflight.json: rendered output paths, warnings, prerequisites, and next steps.
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:
- Kubernetes 1.35 or later, enforced NetworkPolicies, and exact API/database egress destinations.
- Envoy Gateway, cert-manager, and, unless external sign-in disables native admin, wildcard DNS and TLS for native admin and the shared cookie parent domain.
- A default ReadWriteOnce storage class for dedicated Codex workspace claims and
runtime.gatewayStorageClassNamefor gateway state. - For Codex, the configured localhost seccomp profile installed and verified on
every node selected by
runtime.nodeSelector. - For Slack, a proxy route for both gateway runtime traffic and Console
directory lookup. The managed proxy serves both; external proxies need both
URLs. Rendering proxy wiring does not enable a Slack consumer. Either profile
can install the managed
proxy for API directory lookup and dedicated gateway use, but Slack-enabled
Agents require dedicated Codex execution. The example input sets
managedSlackProxy: trueto use the chart-managed proxy Service. Its network policy allows public IPv4 HTTPS, excluding private and reserved ranges, and the proxy authorizes Slack hostnames, so connectivity survives Slack DNS rotation. For an external proxy, omitmanagedSlackProxyand provide literal IPv4runtimeProxyUrlanddirectoryProxyUrlinputs. - For Codex hosted plugin discovery and runtime authentication, a
same-Namespace
codex_pattoken Secret or a PAT entered during Agent creation. See discovery egress. - For optional OCE-managed ChatGPT service-account runtime credentials, the admin Secret, workspace authority, and app connections described in Configure the ChatGPT Backend. Treat managed issuance as unverified until the admin credential flow is separately proven.
- For repositories, create the registry ConfigMap after the first bootstrap
creates Namespace IDs, then rerender with repository support enabled.
Preserve the existing installation's approved GitHub ranges in
repository.upstreamCidrs. For a new installation, obtain the current API and Git IPv4 ranges from GitHub Meta, following the repository installation guide. Do not replace provider ranges with a snapshot of DNS answers: GitHub rotates addresses within those ranges. Both profiles pass these CIDRs through unchanged.
