Skip to main content

Helm + CaSc

Use Helm + CaSc when you want a repeatable Synth installation on Kubernetes with the official private Helm chart.

CaSc means Configuration-as-Code. The Helm chart renders a CaSc document from your values, mounts it into the pod, and imports it before the web app starts.

Helm values
-> CaSc file
-> Project definition, schema, credentials, and watchers
-> published Project evidence
-> Explore, CLI, REST, SDK, or MCP reads

Keep secrets in Kubernetes Secrets, not in Helm values.

This guide builds a generic team-docs Project. Its two Git repositories have different jobs:

source repository -> Git watcher -> team-docs Project -> sealed read snapshot
|
+-> optional remote Git backend for Project storage

The watcher ingests approved source files. The optional backend stores the Synth Project itself so that its knowledge files can be shared and backed up. Canonical Project search and exact-page reads do not require a model provider; the provider settings below enable model-assisted extraction, linking, and linting and may be removed when those jobs are not needed.

Prerequisites

  • Kubernetes cluster
  • kubectl
  • Helm 3
  • Persistent storage
  • A provider key for OpenAI, Anthropic, or OpenCode if this configuration enables model-assisted ingestion, linking, or lint
  • A signed Synth license key
  • Access to the official Synth image registry and Helm chart registry

1. Create The Namespace

kubectl create namespace synth --dry-run=client -o yaml | kubectl apply -f -

2. Authenticate The Private Registries

Use the registry pull key supplied with your licensed deployment materials:

cat secrets/gcp-artifact-registry-pull-key.json | helm registry login us-central1-docker.pkg.dev \
--username _json_key \
--password-stdin

kubectl -n synth create secret docker-registry synth-registry-pull \
--docker-server=us-central1-docker.pkg.dev \
--docker-username=_json_key \
--docker-password="$(cat secrets/gcp-artifact-registry-pull-key.json)" \
--dry-run=client -o yaml | kubectl apply -f -

3. Create Runtime Secrets

This complete example enables OpenCode-assisted jobs and reads private Git repositories. Omit OPENCODE_API_KEY and the provider/agent blocks if you do not enable model-assisted jobs. Omit GITHUB_PAT and the Git credential if both repositories are public; in that case, also remove credential_id from the backend and credential from the watcher:

kubectl -n synth create secret generic synth-runtime-secrets \
--from-literal=OPENCODE_API_KEY="$OPENCODE_API_KEY" \
--from-literal=GITHUB_PAT="$GITHUB_PAT" \
--dry-run=client -o yaml | kubectl apply -f -

Create the license Secret from the signed license key file:

kubectl -n synth create secret generic synth-license \
--from-file=license-key=/secure/path/synth-license-key.txt \
--dry-run=client -o yaml | kubectl apply -f -

4. Write A Values File

Create values-synth.yaml. This full values file creates a Project named team-docs, stores its Synth knowledge files under projects/team-docs in an optional remote backend, and watches documentation in a separate source repository. Replace every your-org URL, hostname, category, and file pattern with values that describe your own knowledge boundary.

# values-synth.yaml
synth:
publicUrl: https://synth.example.com
existingLicenseSecret: synth-license

casc:
raw: |
global_settings:
default_provider: opencode
default_model: gpt-5.4-mini
agents:
extract:
provider: opencode
model: gpt-5.4-mini
temperature: 0.35
max_tokens: 4096
batch_size: 2
link:
provider: opencode
model: gpt-5.4-mini
temperature: 0.25
max_tokens: 4096
lint:
provider: opencode
model: gpt-5.4-mini
temperature: 0.3
max_tokens: 4096
auto_lint_enabled: false
interval_hours: 6
page_limit: 50
providers:
opencode:
enabled: true
credential_id: helm-opencode
base_url: https://opencode.ai/zen/v1
default_model: gpt-5.4-mini
max_retries: 3
retry_delay: 2
timeout: 60

credentials:
helm-opencode:
type: opencode_api_key
name: Helm OpenCode API key
data:
api_key:
env: OPENCODE_API_KEY
github-read-token:
type: github_pat
name: GitHub read token
data:
token:
env: GITHUB_PAT

# CaSC credentials are always Global and organization-wide.

projects:
team-docs:
backend:
type: git
config:
remote_url: https://github.com/your-org/synth-knowledge.git
branch: main
auto_push: true
auto_pull: true
sync_on_create: true
project_subdir: projects/team-docs
credential_id: github-read-token
schema:
categories:
- name: Guides
directory: guides
description: Task-oriented instructions for the team.
- name: Reference
directory: reference
description: Commands, configuration fields, and technical reference.
- name: Troubleshooting
directory: troubleshooting
description: Known failure modes, operational checks, and remediation steps.
agents:
extract:
temperature: 0.25
max_tokens: 4096
link:
temperature: 0.2
max_tokens: 4096
lint:
temperature: 0.25
max_tokens: 4096
watchers:
team-docs-main:
type: git
repo: https://github.com/your-org/docs-site.git
branch: main
interval: 300
auto_start: true
credential: github-read-token
files:
rules:
- name: Team documentation
include:
- "docs/**/*.{md,mdx,markdown}"
exclude:
- "**/node_modules/**"
- "**/build/**"

secrets:
# This Secret is mounted into the container with envFrom by the Helm chart.
# It must contain OPENCODE_API_KEY and GITHUB_PAT for the CaSc credentials above.
existingSecret: synth-runtime-secrets

image:
repository: us-central1-docker.pkg.dev/project-96337d0a-4d88-427b-af7/synth/synth
tag: latest

imagePullSecrets:
- name: synth-registry-pull

ingress:
enabled: true
className: nginx
hosts:
- host: synth.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: synth-tls
hosts:
- synth.example.com

persistence:
size: 50Gi

resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "4"
memory: 8Gi

This complete values file shows the important production pieces together:

  • synth.casc.raw provides the full CaSc document imported at startup.
  • global_settings.providers.opencode.credential_id points at an env-backed Synth credential, not a literal API key.
  • credentials creates organization-wide Global Synth credentials from Kubernetes Secret environment variables. Secret values are never stored in Helm values.
  • projects.team-docs.backend imports the Project from the remote Git backend during startup when sync_on_create: true.
  • projects.team-docs.watchers.team-docs-main watches the approved source files and starts automatically.
  • secrets.existingSecret must name the Kubernetes Secret that contains the optional OPENCODE_API_KEY and GITHUB_PAT values used by this example.
  • synth.existingLicenseSecret must name the Kubernetes Secret that contains the signed Synth license under key license-key.
  • synth.publicUrl supplies the public origin; the chart derives the normal host and CORS defaults from it.
  • image and imagePullSecrets point Kubernetes at the official private Synth image.
  • ingress, persistence, and resources are normal Helm chart values, outside CaSc. Synth and the chart supply the runtime, probe, and worker defaults.

Before installing, change the host, repository URLs, Project subdirectory, schema categories, watcher rules, Secret names, image version, resource sizes, and storage size for your environment.

GitLab watcher credentials

GitLab tokens follow the same Kubernetes Secret -> container environment -> CaSc credential-reference path as other watcher secrets. Do not put the token in values.yaml, synth.casc.raw, a watcher config, or a Git URL.

Add a key named exactly GITLAB_PAT to the runtime Secret Synth already uses. For a new installation, load it from an access-controlled file so it does not appear in Helm values or shell history:

kubectl -n synth create secret generic synth-runtime-secrets \
--from-file=GITLAB_PAT=/secure/path/gitlab-read-token \
--dry-run=client -o yaml | kubectl apply -f -

If the Secret already exists, add GITLAB_PAT through the Secret manager that owns it; do not apply a partial replacement that would remove its other keys.

Then reference the environment variable from a Synth credential and point the watcher at the group:

synth:
credentials:
gitlab-read-token:
type: gitlab_pat
name: GitLab read token
data:
token:
env: GITLAB_PAT

projects:
- name: platform-docs
watchers:
platform-group:
type: git
group: https://gitlab.com/acme/platform
credential: gitlab-read-token

secrets:
existingSecret: synth-runtime-secrets

monitoring:
enabled: true
serviceMonitor:
enabled: true
prometheusRule:
enabled: true
runbookUrl: https://synthkb.dev/docs/guides/watchers#git-watcher-operations

That is the complete common-case watcher configuration. Synth infers GitLab from group, starts the watcher automatically, polls collections every five minutes, includes subgroups, excludes shared and archived projects, watches common knowledge-file formats, applies managed-clone limits, and bounds catalog history. Set only the fields whose defaults you intentionally want to change.

The chart exposes secrets.existingSecret through envFrom in both the web and ingestion-worker containers. The web process validates and displays the watcher; the worker owns background clone, freshness, and reconcile work when enabled.

The clone defaults bound Synth-managed checkout storage; a rejected clone is removed before its checkpoint can advance. Catalog defaults bound durable generation/action diagnostics while preserving the active recovery generation. The Prometheus rule alerts on stale/incomplete catalogs, repository failures, blocked removals, and warning/critical clone capacity. The chart requires the Prometheus Operator CRDs but does not install them.

Before installing, render the manifests and confirm only Secret references—not the token value—appear:

helm template synth deploy/charts/synth -n synth -f values-synth.yaml \
> /tmp/synth-rendered.yaml
grep -A2 -n 'secretRef:' /tmp/synth-rendered.yaml

After rollout, confirm both containers received GITLAB_PAT without printing its value:

for container in synth ingestion-worker; do
kubectl -n synth exec deploy/synth -c "$container" -- \
sh -c 'test -n "$GITLAB_PAT" && echo "$HOSTNAME: GITLAB_PAT is set"'
done

For GitLab.com, no provider or enablement setting is required. Self-managed GitLab is not released and has no Helm or environment allowlist switch. Stop individual watchers from the Watchers page for a non-destructive rollback; their saved state and already-published knowledge remain intact.

5. Install

helm upgrade --install synth \
oci://us-central1-docker.pkg.dev/project-96337d0a-4d88-427b-af7/synth/charts/synth \
--namespace synth \
-f values-synth.yaml \
--wait \
--timeout 10m

6. Verify The Deployment

Check Kubernetes:

kubectl -n synth rollout status deploy/synth --timeout=10m
kubectl -n synth get deploy,svc,ingress,pvc

Check health:

curl -fsS https://synth.example.com/health

Open https://synth.example.com/setup after a fresh install and create the first admin user. Then verify:

  • Projects includes the team-docs Project.
  • Its backend points at your Project-storage repository and projects/team-docs.
  • Watchers includes team-docs-main and reports a successful source sync.
  • The watcher rules match the source files you intended to approve.
  • The helm-opencode and github-read-token credentials exist without exposing secret values, when those optional credentials are configured.

You can also inspect the imported project config from the pod:

kubectl -n synth exec deploy/synth -- \
sed -n '1,260p' /var/lib/synth/projects/team-docs/.synth/config.yaml

The watcher section should include:

watchers:
team-docs-main:
type: git
repo: https://github.com/your-org/docs-site.git
branch: main
interval: 300
auto_start: true
credential: github-read-token
files:
rules:
- name: Team documentation
include:
- docs/**/*.{md,mdx,markdown}

7. Verify Project Evidence

Open Explore, select team-docs, and search for a phrase that exists in one of the watched files. Confirm that the result identifies the real source page and provenance, then open the page and inspect its exact content. This is the important end-to-end check: a healthy pod alone does not prove that the Project published readable evidence.

For a command-line check, create a Project-scoped API key with only knowledge:read, then use the standalone Synth CLI:

synth login https://synth.example.com
synth projects
synth use team-docs
synth status
synth search "a phrase from your source documentation"
synth page PAGE_ID

synth status should report a ready Project with an active snapshot. Search should return ranked source evidence, and synth page should return the exact page selected from those results. If status reports index_unready, inspect the failed publication or ingestion operation; normal reads do not build or repair Project state.

Remote Git Project Storage

Use a remote Git backend when the Synth project itself should live in a shared Git repository. Use a Git watcher when an external source repository should feed knowledge into that project.

See Remote Git Backends for storage setup and Watchers for continuous source sync.

Upgrades

Change the values file, then run:

helm upgrade synth \
oci://us-central1-docker.pkg.dev/project-96337d0a-4d88-427b-af7/synth/charts/synth \
--namespace synth \
-f values-synth.yaml \
--wait \
--timeout 10m

The app imports CaSc on startup, so changes to provider defaults, projects, and watchers apply when the pod rolls.

Fresh Reinstall

Normal upgrades keep the PVC to protect project data. Delete the PVC only when you intentionally want a brand-new application and have confirmed there is no data you need to keep:

helm uninstall synth -n synth
kubectl -n synth delete pvc synth-data --ignore-not-found

Then install again from the same values file and complete first-admin setup.

Troubleshooting

SymptomWhat to check
Pod starts but settings are emptyConfirm the values rendered CaSc and the pod restarted.
Provider test failsConfirm the runtime Secret contains the provider key.
Public login failsCheck synth.publicUrl, allowedHosts, and corsOrigins.
Watcher is unhealthyCheck watcher logs and Git credentials.
Project is unreadyInspect the failed ingestion/publication operation; do not retry reads in a loop.
Search returns no expected pageConfirm the watcher include rules matched real files and completed successfully.
Fresh reinstall still has old dataDelete the retained PVC after uninstall.