Install (AWS Marketplace)
These steps were verified end to end on Amazon EKS: a clean install with chart 0.2.5 (organization auto-created, sign-in with the generated administrator password), and governance policies through an S3 bucket with chart 0.2.6.
1. Authenticate Helm to the Marketplace registry
aws ecr get-login-password --region us-east-1 \
| helm registry login 709825985650.dkr.ecr.us-east-1.amazonaws.com \
--username AWS --password-stdin
2. Create the namespace and license secret
From the directory where you unpacked your license bundle:
kubectl create namespace openbox
kubectl -n openbox create secret generic openbox-license \
--from-file=token=./license.jwt \
--from-file=report-signing-key=./report-signing.key
The secret must be named openbox-license with exactly these two keys.
If the secret is missing, the pre-install license check cannot start: its pod waits forever for the volume, and helm install hangs with no error until it times out.
3. Prepare your public URLs
The console and the API must know the public addresses they are served on. Save this as public-urls.yaml, replacing YOUR_DOMAIN with the domain you pass in step 4:
openbox-fe:
extraEnv:
- { name: VITE_API_URL, value: "https://openbox-api.YOUR_DOMAIN" }
- { name: VITE_CORE_API_URL, value: "https://openbox-core.YOUR_DOMAIN" }
- { name: VITE_WS_URL, value: "https://openbox-api.YOUR_DOMAIN" }
- { name: VITE_KEYCLOAK_BASE_URL, value: "https://identity.YOUR_DOMAIN" }
- { name: VITE_KEYCLOAK_CLIENT_ID, value: "openbox" }
- { name: VITE_KEYCLOAK_SHARED_REALM, value: "openbox" }
openbox-backend:
secret:
stringData:
KEYCLOAK_BASE_URL: "https://identity.YOUR_DOMAIN"
FRONTEND_URL: "https://openbox-eks.YOUR_DOMAIN"
ALLOWED_ORIGINS: "https://openbox-eks.YOUR_DOMAIN"
KEYCLOAK_CLIENT_ROOT_URL: "https://openbox-eks.YOUR_DOMAIN/*"
KEYCLOAK_CLIENT_HOME_URL: "https://openbox-eks.YOUR_DOMAIN/"
KEYCLOAK_CLIENT_REDIRECT_URIS: "https://openbox-eks.YOUR_DOMAIN/*"
KEYCLOAK_CLIENT_WEB_ORIGINS: "https://openbox-eks.YOUR_DOMAIN"
KEYCLOAK_CLIENT_ADMIN_URL: "https://openbox-eks.YOUR_DOMAIN/*"
KEYCLOAK_CLIENT_POST_LOGOUT_REDIRECT_URIS: "https://openbox-eks.YOUR_DOMAIN/*"
Without this file the console cannot reach the API, and Keycloak rejects the browser's login redirect.
The redirect and origin addresses are written into your organization's identity client when the organization is created. Changing them afterwards does not update an organization that already exists.
4. Install the chart
helm install openbox \
oci://709825985650.dkr.ecr.us-east-1.amazonaws.com/openbox-ai/openbox/charts/openbox \
--version 0.2.6 \
--namespace openbox \
--set global.ingress.domain=YOUR_DOMAIN \
--set global.license.expectedTenantId=YOUR_TENANT_ID \
--set global.seedOrg.adminEmail=YOUR_ADMIN_EMAIL \
--set global.recaptcha.disabled=true \
-f public-urls.yaml
| Value | Required | Notes |
|---|---|---|
global.ingress.domain | yes | Your DNS domain |
global.license.expectedTenantId | yes | Exactly as issued. The chart ships the placeholder REPLACE_WITH_YOUR_TENANT_ID |
global.seedOrg.adminEmail | yes | Your first administrator, company domain. Missing, malformed, or still the placeholder REPLACE_WITH_ADMIN_EMAIL → the install stops within seconds, before anything is created |
global.recaptcha.disabled | if no Google keys | Default false (reCAPTCHA on). One value controls both the backend and the console |
Passwords for the bundled PostgreSQL, Keycloak and Temporal are generated inside your cluster during install and stored in openbox-gen-* secrets. Do not supply your own — the chart schema rejects inline credentials by design.
Storage class
If your cluster has no default StorageClass, name one for each volume:
--set postgresql.primary.persistence.storageClass=YOUR_CLASS \
--set openbox-core.redis.storageClass=YOUR_CLASS \
--set identity-service.persistence.storageClassName=YOUR_CLASS
Note the third key is spelled storageClassName.
Optional: voice agent executor and ElevenLabs monitor
Both ship disabled. Enable with --set openbox-executor.enabled=true (served at executor.YOUR_DOMAIN) and --set openbox-evri-monitor.enabled=true (served at evri-monitor.YOUR_DOMAIN), and add DNS records for them.
-
The executor does not start until its governance credentials exist. Create them in the console after your organization exists, then:
kubectl -n openbox create secret generic openbox-executor-governance \
--from-literal=OPENBOX_API_KEY=obx_live_... \
--from-literal=OPENBOX_AGENT_DID=did:aip:<uuid> \
--from-literal=OPENBOX_AGENT_PRIVATE_KEY=...
kubectl -n openbox rollout restart deployment/openbox-executor-serviceThe API key must start with
obx_live_orobx_test_; the DID must bedid:aip:<uuid>. -
The monitor needs the webhook signing secret ElevenLabs issues: store it in a Secret (key
ELEVENLABS_WEBHOOK_SECRET) and add--set openbox-evri-monitor.webhookSecret.existingSecret=YOUR_SECRET.
Governance policies (S3 bucket)
Policies and behavior rules are compiled into one bundle that the backend writes to S3 and the policy engine (OPA) reads. Without a bucket the platform runs, but saving a policy or behavior rule fails, and OPA runs with no policy, so every action is allowed.
-
Create a private S3 bucket with default encryption. It also stores audit and evidence exports, so dedicate it to this install.
-
Create two IAM roles that trust your cluster's OIDC provider (IRSA), condition
sts.amazonaws.comaudience and the service-account subject:Role Service account ( <namespace>:<name>)Permissions OPA reader openbox:opa-s3-bundle-sas3:GetObjectonYOUR_BUCKET/bundle.tar.gzonlyBackend writer openbox:openbox-backend-roles3:PutObjectonYOUR_BUCKET/bundle.tar.gz;s3:PutObject,s3:GetObject,s3:DeleteObjectonYOUR_BUCKET/exports/* -
Add to the install (or upgrade) command:
--set global.policyBundle.bucket=YOUR_BUCKET \
--set global.policyBundle.region=YOUR_BUCKET_REGION \
--set global.policyBundle.opaRoleArn=arn:aws:iam::ACCOUNT:role/OPA_READER \
--set global.policyBundle.backendRoleArn=arn:aws:iam::ACCOUNT:role/BACKEND_WRITER
Optional global.policyBundle.keyPrefix (for example openbox/) places the bundle at <prefix>bundle.tar.gz; scope both roles to that key. Leave backendRoleArn empty if the backend gets AWS credentials through EKS Pod Identity. Enabling or changing these values on an upgrade restarts the backend and OPA.
Until the first policy is saved, OPA logs Bundle load failed: server replied with Forbidden on every poll — there is no bundle yet. That stops after the first save.
Without a GPU node
--set llamafirewall.enabled=false
5. Wait for convergence
kubectl -n openbox get pods -w
Allow about 10 minutes. Pods that depend on the database schema or the workflow engine show Init until it is ready; their init container log says what they are waiting for. One Temporal pod may restart once while the services register; repeated restarts mean a real fault — see Troubleshooting.
Confirm the organization was created:
kubectl -n openbox logs -l app.kubernetes.io/name=openbox-backend-service -c create-organization
On the backend's first start this prints organization created; on later starts, organization already exists — nothing to do.
6. Create DNS records
The ingress load balancer exists only after install, so create records now. Point each hostname at it:
| Hostname | Serves |
|---|---|
openbox-eks.YOUR_DOMAIN | Web console |
openbox-api.YOUR_DOMAIN | REST API |
openbox-core.YOUR_DOMAIN | Core service |
identity.YOUR_DOMAIN | Keycloak |
opa.YOUR_DOMAIN | Policy engine |
7. Sign in
Your organization and administrator already exist. Get the generated password:
kubectl -n openbox get secret openbox-seed-admin \
-o jsonpath='{.data.password}' | base64 -d; echo
Open https://openbox-eks.YOUR_DOMAIN and sign in with YOUR_ADMIN_EMAIL and that password. Change it after the first sign-in.
To choose the password yourself, create a secret with a password key before installing and add --set global.seedOrg.existingSecret=YOUR_SECRET_NAME.
Upgrading
helm upgrade openbox \
oci://709825985650.dkr.ecr.us-east-1.amazonaws.com/openbox-ai/openbox/charts/openbox \
--version NEW_VERSION --namespace openbox \
--reset-then-reuse-values -f public-urls.yaml
Use --reset-then-reuse-values, not --reuse-values: the latter keeps the old chart's defaults and silently ignores settings a new version introduces.
Uninstalling
helm uninstall openbox --namespace openbox
Generated secrets and volumes are kept on purpose, so a reinstall over the same volumes still has matching credentials. To remove all data, delete the PersistentVolumeClaims together with the openbox-gen-* and openbox-seed-admin secrets:
kubectl -n openbox delete pvc --all
kubectl -n openbox get secret -o name | grep -E 'openbox-gen-|openbox-seed-admin' | xargs kubectl -n openbox delete
Deleting the secrets but keeping the volumes leaves the database with passwords that no longer match: PostgreSQL crash-loops and the only recovery is deleting the volume.