Kubernetes Installation
See Obtaining the Software for
how to get the symphony-<version>.tgz Helm chart.
Deploy Symphony into an existing Kubernetes cluster using the provided Helm chart. This method is suited for organisations that already operate Kubernetes infrastructure.
Prerequisites
- Kubernetes 1.26+
- Helm 3.10+
- An ingress controller (handling ports 80, 443 and 4222) such as ingress-nginx or a Gateway API implementation
- A DNS name that resolves to the ingress controller's or gateway's external IP
- An OIDC provider (Auth0, Keycloak, Okta, or similar)—or use the bundled Keycloak option described later in this guide
- TLS certificates (or cert-manager for automated provisioning)
Installation
1. Load the container image
The Symphony container image is distributed inside the Docker Compose
archive (cirata-symphony-<version>-docker.tar.gz) under images/.
Extract the image and load it into your container registry or directly
into cluster nodes:
# Extract the image from the Docker archive
tar xzf cirata-symphony-*-docker.tar.gz --wildcards '*/images/*.tar'
# Option A: Push to a container registry
docker load -i symphony-*-docker/images/cirata-*.tar
docker tag cirata:latest registry.example.com/cirata:latest
docker push registry.example.com/cirata:latest
# Option B: Load directly into cluster nodes (for testing)
docker load -i symphony-*-docker/images/cirata-*.tar
2. Install the Helm chart
With an external OIDC provider, you can supply OIDC values in the Helm command to pre-populate the setup wizard, or omit them and enter them in the wizard instead:
# Minimal — OIDC configured in the setup wizard
helm install symphony symphony-*.tgz \
--set hostname=symphony.example.com
# With OIDC pre-populated (for automated deployments)
helm install symphony symphony-*.tgz \
--set hostname=symphony.example.com \
--set oidc.issuer=auth.example.com \
--set oidc.clientid=symphony-api \
--set oidc.uiclientid=symphony-ui
Or with the bundled Keycloak (no external OIDC provider required):
helm install symphony symphony-*.tgz \
--set hostname=symphony.example.com \
--set keycloak.enabled=true \
--set keycloak.adminPassword=changeme
If you pushed the image to a registry, also set:
--set image.repository=registry.example.com/cirata \
--set image.tag=latest
3. Verify the deployment
kubectl get pods
kubectl get svc
kubectl get ingress
4. Complete setup
Open https://<hostname> in your browser and follow the administration
setup wizard. If you did not supply OIDC values in the Helm command, the
wizard provides fields for the issuer URL and client IDs along with a
Validate Configuration button that checks your DNS and OIDC settings
before you submit. Each check includes a description of any issue and
instructions for resolving it.
For help registering clients with your identity provider, see OIDC Configuration. For a detailed explanation of each validation check, see Setup Wizard Validation.
5. Create users in Keycloak
If you enabled bundled Keycloak, you need to create user accounts before anyone can sign in to Symphony.
- Open the Keycloak admin console at
https://<hostname>/auth - Sign in with username
adminand thekeycloak.adminPasswordyou set during install - Select the symphony realm from the dropdown in the top-left corner
- Navigate to Users and click Create new user
- Fill in the username and email, then click Create
- Go to the Credentials tab, click Set password, enter a password, and toggle Temporary off if you do not want the user to be forced to change it on first login
- Click Save
Users can now sign in to Symphony at https://<hostname> using these
credentials.
Bundled Keycloak
The Helm chart includes an optional bundled Keycloak instance that provides a turnkey OIDC setup. When enabled, Keycloak is deployed alongside Symphony with a pre-configured realm containing:
- symphony-api—a confidential client for backend token validation (no login flow enabled)
- symphony-ui—a public client for the browser-based SPA
- Roles—
symphony-adminandviewerrealm roles - Groups mapper—maps realm roles into the
groupstoken claim
Symphony's OIDC settings are automatically configured to use the bundled Keycloak instance. No manual OIDC configuration is needed.
Keycloak values
| Value | Default | Description |
|---|---|---|
keycloak.enabled | false | Enable the bundled Keycloak instance |
keycloak.image | quay.io/keycloak/keycloak:26.1 | Keycloak container image |
keycloak.adminUser | admin | Keycloak admin username |
keycloak.adminPassword | "" | Keycloak admin password (required when enabled) |
keycloak.persistence.size | 1Gi | Storage size for Keycloak data |
keycloak.persistence.keepOnUninstall | false | When true, annotates the Keycloak PVC with helm.sh/resource-policy: keep so helm uninstall does not delete it |
Helm Values
The following values can be configured:
| Value | Default | Description |
|---|---|---|
hostname | symphony.example.com | Public DNS hostname |
listen_address | 0.0.0.0 | Listen address |
ui_port | 8080 | Internal HTTP port |
websocket_port | 9222 | Internal WebSocket port |
nats_port | 4222 | Internal NATS port |
oidc.issuer | "" | OIDC issuer URL (optional—can be entered in the setup wizard) |
oidc.clientid | "" | OIDC client ID for API (optional—can be entered in the setup wizard) |
oidc.uiclientid | "" | OIDC client ID for UI (optional—can be entered in the setup wizard) |
oidc.clientsecret | "" | Client secret for the API client (optional, for confidential clients) |
oidc.uiclientsecret | "" | Client secret for the UI client (optional, for confidential UI clients) |
oidc.caCert | "" | Path (inside pod) to CA cert PEM for OIDC TLS—use with tls.caSecretName to mount it. Validated during setup when set. |
oidc.tlsSkipVerify | false | Skip TLS verification for OIDC—can also be set via the setup wizard. Use oidc.caCert for production. |
oidc.internalBaseURL | "" | Internal URL for OIDC provider |
image.repository | cirata | Container image repository |
image.tag | latest | Container image tag |
image.pullPolicy | IfNotPresent | Image pull policy |
ingress.enabled | true | Enable ingress resource |
ingress.className | nginx | Ingress class name |
tls.enabled | false | Enable TLS on ingress |
tls.secretName | "" | TLS secret name |
tls.caSecretName | "" | TLS CA secret name |
persistence.storageClass | "" | Storage class (empty for cluster default) |
persistence.size | 1Gi | Storage size for Symphony data |
persistence.accessMode | ReadWriteOnce | PVC access mode |
persistence.keepOnUninstall | false | When true, annotates the PVC with helm.sh/resource-policy: keep so helm uninstall does not delete it. See Data persistence and backup below. |
nats.tcpPassthrough | false | Create TCP ConfigMap for NATS ingress passthrough |
nats.tls.enabled | false | Enable native TLS on the NATS listener (port 4222) |
nats.tls.secretName | "" | Kubernetes TLS secret containing tls.crt and tls.key |
nats.tls.caSecretName | "" | Kubernetes secret containing ca.crt (optional, for client verification) |
Override values using --set flags or a custom values file:
helm install symphony symphony-*.tgz -f my-values.yaml
Data Persistence and Backup
Symphony stores all durable state—operator and account signing seeds, account JWTs, users, roles, API keys, licensing records, and every user's private data—in the /config directory that the chart mounts from a PersistentVolumeClaim. This directory must be protected. The identity seeds inside it are the trust root for every JWT and API token Symphony has ever issued; losing them invalidates every extension credential and every user token in circulation.
PersistentVolume reclaim policy
Most Kubernetes dynamic provisioners create StorageClasses with a reclaim policy of Delete. That means a plain helm uninstall releases the PVC, which causes the cluster to permanently delete the underlying volume and all Symphony state. This default is dangerous for any deployment that holds real data.
Three complementary controls are available:
-
Take regular external backups. This is the only defence against cluster loss, storage corruption, or accidental deletion. See Backup and Recovery in the operations guide for procedures. Reclaim-policy and annotation tricks are not a substitute for backups.
-
Preserve the PVC across
helm uninstall. Setpersistence.keepOnUninstall=true(andkeycloak.persistence.keepOnUninstall=truewhen Keycloak is bundled). The chart stampshelm.sh/resource-policy: keepon the PVC so Helm will not delete it on uninstall. Existing contents remain intact; to clean up later,kubectl delete pvcexplicitly. -
Change the PV reclaim policy to
Retain. Even with the PVC preserved, a subsequentkubectl delete pvcwill still trigger the StorageClass reclaim policy. For long-lived production deployments, patch the PV after install:PVC=my-release-pvc
NS=Symphony
PV=$(kubectl -n "$NS" get pvc "$PVC" -o jsonpath='{.spec.volumeName}')
kubectl patch pv "$PV" -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'A
RetainedPV requires manual cleanup when the data is genuinely no longer needed.
The chart defaults persistence.keepOnUninstall to false to preserve existing upgrade behaviour. Set it to true for any deployment that holds real data.
Backup approaches
The operations guide covers three procedures in detail. In brief:
cirata symphony backup(recommended)—a single command against the running instance produces a consistent archive of/config. Requires thesymphony-adminrole. See Backup and Recovery.- CSI volume snapshots—if your StorageClass supports them (
kubectl get volumesnapshotclass), these capture a crash-consistent block-level image without any server-side coordination. Schedule with aCronJobor operator such as Velero. - Offline tar—scale the deployment to zero, tar
/configfrom a helper pod mounted at the PVC, scale back up. Used for DR rehearsals and migrations between clusters.
Upstream references: Persistent Volumes, Change reclaim policy, Volume snapshots.
TLS Configuration
TLS is strongly recommended for production deployments. Enable it with
tls.enabled=true and provide a certificate via one of the methods below.
Using cert-manager
If your cluster has cert-manager installed, configure the ingress to request a certificate automatically:
helm install symphony symphony-*.tgz \
--set hostname=symphony.example.com \
--set tls.enabled=true \
--set ingress.annotations."cert-manager\.io/cluster-issuer"=letsencrypt-prod
cert-manager will create a TLS secret automatically and the ingress will use it.
Manual TLS
Create a TLS secret and reference it during installation:
kubectl create secret tls symphony-tls \
--cert=fullchain.pem \
--key=privkey.pem
helm install symphony symphony-*.tgz \
--set hostname=symphony.example.com \
--set tls.enabled=true \
--set tls.secretName=symphony-tls
Optionally create and pass in a CA secret as well:
kubectl create secret generic symphony-tls-ca \
--from-file=ca.crt
helm install symphony symphony-*.tgz \
--set hostname=symphony.example.com \
--set tls.enabled=true \
--set tls.secretName=symphony-tls \
--set tls.caSecretName=symphony-tls-ca
NATS TLS
To encrypt native NATS connections (port 4222) used by extensions and CLI tools, enable NATS TLS separately from the ingress TLS above:
kubectl create secret tls symphony-nats-tls \
--cert=nats-cert.pem \
--key=nats-key.pem
helm install symphony symphony-*.tgz \
--set hostname=symphony.example.com \
--set tls.enabled=true \
--set tls.secretName=symphony-tls \
--set nats.tls.enabled=true \
--set nats.tls.secretName=symphony-nats-tls
When NATS TLS is enabled, the NATS server requires TLS on all connections. Client SDKs automatically attempt TLS using system CAs—no client-side configuration is needed when the certificate is signed by a trusted CA.
For certificates signed by a private CA, extensions can set the
SYMPHONY_TLS_CA environment variable to the path of the CA
certificate — this trust anchor applies to both the HTTPS
registration exchange and NATS. NATS_TLS_CA remains supported
as a NATS-only override.
Using the CLI
The cirata CLI tool lets you manage Symphony from the terminal.
CLI binaries for Linux, macOS, and Windows are included in the
installation archive under the cli/ directory. Copy the binary for
your platform to a directory in your PATH:
# Example: install the macOS binary
chmod +x cli/cirata-darwin-arm64
sudo mv cli/cirata-darwin-arm64 /usr/local/bin/cirata
See the Command-Line Tool page for full setup and usage instructions.
Architecture
The Helm chart deploys the following resources:
- Deployment—a single Symphony pod running the
ciratabinary with thesymphony operatecommand - Service—exposes HTTP (80), WebSocket (81), and NATS (4222) ports
- Ingress—two ingress resources: one for HTTP traffic (
/and optionally/auth), and one for WebSocket connections (/ws) with appropriate proxy settings - ConfigMap—stores environment configuration (hostname, ports, OIDC)
- PersistentVolumeClaim—provides storage for configuration and runtime data
When bundled Keycloak is enabled, the chart additionally deploys:
- Keycloak Deployment—Keycloak 26.1 with auto-imported realm configuration
- Keycloak Service—exposes Keycloak on port 8180
- Keycloak ConfigMap—contains the Symphony realm JSON for auto-import
- Keycloak PVC—persistent storage for Keycloak data
- Ingress
/authpath—routes/authtraffic to Keycloak
Health and Readiness Probes
Symphony provides dedicated endpoints for Kubernetes probes:
| Endpoint | Purpose | Healthy | Degraded |
|---|---|---|---|
GET /api/v1/ready | Readiness probe | 200 with {"ready": true} | 503 with {"ready": false} |
GET /api/v1/health | Liveness / detailed health | 200 with component status and version | 503 with degraded components |
The readiness probe checks that the NATS server is running and able to accept connections. The health probe additionally reports the status of individual components (NATS server, messaging connection) and includes build version information.
The Helm chart includes these probes by default. If you are customising the deployment, use the following configuration:
livenessProbe:
httpGet:
path: /api/v1/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/v1/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Managing the Installation
# View pod logs
kubectl logs -f deployment/symphony
# Upgrade
helm upgrade symphony symphony-NEW_VERSION.tgz
# Uninstall
helm uninstall symphony
# Check configuration
kubectl get configmap symphony-configmap -o yaml
Troubleshooting
- Pod not starting—check
kubectl describe podandkubectl logsfor error messages. Verify the container image is available. - Ingress not working—confirm your ingress controller is running and that DNS resolves to the correct IP. Check ingress controller logs.
- 503 errors—if both
/and/authreturn 503, verify the service endpoints exist (kubectl get endpoints). Also ensure the ingress controller does not have conflictingrewrite-targetannotations. - WebSocket not connecting—the chart creates a separate ingress
resource for
/wswith WebSocket-appropriate proxy settings. Verify both ingress resources exist withkubectl get ingress. - Storage issues—verify the PersistentVolumeClaim is bound
(
kubectl get pvc). If a PVC is stuck in aTerminatingstate from a previous install, you may need to remove its finalizers and force-delete it before reinstalling. - NATS connectivity for extensions—external extensions connect to
NATS via the service on port 4222. If extensions run outside the cluster,
enable
nats.tcpPassthroughand configure your ingress-nginx controller with--tcp-services-configmapto expose port 4222. - Keycloak not starting—verify
keycloak.adminPasswordis set. Check Keycloak pod logs withkubectl logs -f deployment/symphony-keycloak. The realm import runs on first startup and may take a minute. - Users cannot sign in (bundled Keycloak)—ensure you have created user accounts in Keycloak under the symphony realm. See Create users in Keycloak above.
- Empty credentials from
/api/v1/usertoken—this usually means the Symphony pod cannot reach its own NATS server. The chart useshostAliasesto route the external hostname to127.0.0.1within the pod. Verify withkubectl get pod <pod> -o jsonpath='{.spec.hostAliases}'.