Helm reference
Deploy canette to your cluster using the Helm chart.
The canette Helm chart deploys the API, UI, builder, controller, logstreamer, and BuildKit daemon to your cluster. PostgreSQL and an in-cluster image registry are included and enabled by default.
Prerequisites
- Helm 3.8+
- Kubernetes 1.27+ with Gateway API CRDs installed
- A Gateway API implementation (Traefik, Cilium, etc.)
- A pre-existing Gateway resource — see Installation for the full setup checklist
Install
The chart is published to GitHub Container Registry. Create a values.yaml and install:
helm install canette oci://ghcr.io/canette/canette \
-n canette-system --create-namespace \
-f values.yamlOmitting --version installs the latest release. To pin a specific version:
helm install canette oci://ghcr.io/canette/canette \
--version 1.2.3 \
-n canette-system --create-namespace \
-f values.yamlAvailable versions are listed on the canette package page.
The -n flag controls the system namespace. canette always creates a second namespace for build jobs named {system}-build — with the default above that becomes canette-system-build.
Install from source
If you have the repository cloned, you can install directly from the chart directory:
helm install canette charts/canette \
-n canette-system --create-namespace \
-f values.yamlMultiple instances
Because all namespaces are derived from -n, you can run isolated installations side by side:
# Production — apps at {app}-{project}.apps.example.com
helm install canette oci://ghcr.io/canette/canette \
-n canette-system --create-namespace -f prod-values.yaml
# Staging — apps at {app}-{project}.staging.example.com
helm install canette-staging oci://ghcr.io/canette/canette \
-n canette-staging --create-namespace -f staging-values.yamlThis creates canette-system + canette-system-build for production, and canette-staging + canette-staging-build for staging — no resource conflicts.
Key values
The sections below cover the most commonly used values. For the full list with descriptions, see charts/canette/values.yaml in the repository.
Required
| Value | Description |
|---|---|
domain | Base domain for all dynamic hostnames, e.g. apps.example.com. App URLs use {app}-{project}.<domain>; the in-cluster registry is exposed at registry.<domain>. |
ui.hostname | Public hostname for the UI, e.g. canette.example.com |
Authentication (optional)
Email and password login is enabled by default — no OAuth app required. To add GitHub or Google OAuth:
| Value | Description |
|---|---|
api.githubClientId | GitHub OAuth app client ID |
api.githubClientSecret | GitHub OAuth app client secret (or use api.githubClientSecretRef) |
api.googleClientId | Google OAuth client ID |
api.googleClientSecret | Google OAuth client secret (or use api.googleClientSecretRef) |
Images (optional)
All service images default to {image.repo}/{serviceName}:{image.tag}, where image.tag defaults to the chart's appVersion. Installing or upgrading the chart automatically uses the matching image versions — no image overrides needed for a standard install.
| Value | Default | Description |
|---|---|---|
image.repo | ghcr.io/canette | Base repository for all canette service images |
image.tag | Chart.AppVersion | Tag applied to all service images. Set to edge to use the latest development build. |
api.image | derived | Override the API image |
ui.image | derived | Override the UI image |
builder.image | derived | Override the builder image |
builder.builderImage | derived | Override the Railpack+buildctl build job image |
builder.gitInitImage | derived | Override the git-init build job image |
controller.image | derived | Override the controller image |
logstreamer.image | derived | Override the logstreamer image |
migrations.enabled | false | Set to true to run migrations as a Helm pre-upgrade Job instead of at API startup |
Do not use :edge in production. The edge tag is rebuilt on every commit to main and is not immutable — a pod restart can silently pull a newer image, leaving components running at mismatched versions. Stick with the chart's default versioned tag for any environment where stability matters.
API configuration (optional)
| Value | Default | Description |
|---|---|---|
api.defaultResources.cpuRequest | 100m | Default CPU request for every app deployment |
api.defaultResources.memoryRequest | 128Mi | Default memory request for every app deployment |
api.defaultResources.cpuLimit | 500m | Default CPU limit for every app deployment |
api.defaultResources.memoryLimit | 512Mi | Default memory limit for every app deployment |
api.webhookBaseUrl | "" | Public base URL used when constructing webhook URLs registered with git providers. Leave empty to use the UI hostname. Set this if your API is accessible at a different hostname than the UI. |
api.disableEmailSignup | false | Hard global disable for email sign-up. Overrides the admin UI signup mode setting. Use for deployments where email accounts should be permanently locked at the infrastructure level. See Authentication — Hard disable via Helm. |
api.email.provider | "" | Email provider for magic-link login. Supported values: resend. Leave empty to disable magic links. |
api.email.resendApiKey | "" | Resend API key. Used when api.email.provider=resend. Use api.email.resendApiKeyRef to reference an existing Secret. |
api.email.from | "canette <noreply@example.com>" | From address for outgoing emails. Must match a verified sending domain in your email provider. |
Resource values use standard Kubernetes quantity notation: 100m (millicores), 256Mi (mebibytes), 1 (full core or gigabyte).
These values are snapshotted into each deployment at trigger time. Changing them takes effect on the next deployment — existing live apps are not affected until redeployed.
Infrastructure (optional)
| Value | Default | Description |
|---|---|---|
postgres.enabled | true | Deploy in-cluster PostgreSQL |
postgres.password | "" | Override auto-generated postgres password (leave empty to auto-generate) |
externalDatabase.url | "" | External DB connection string when postgres.enabled=false |
externalDatabase.ssl.rejectUnauthorized | true | Verify the database server's TLS certificate. Set to false only for dev/testing — disables MITM protection. |
registry.enabled | true | Deploy in-cluster image registry, exposed via HTTPRoute at registry.<domain> |
registry.username | "" | Override auto-generated registry username (both fields required to override) |
registry.password | "" | Override auto-generated registry password |
controller.pullRepo | "" | Registry URL for kubelet image pulls. Auto-derives to registry.<domain>/ when registry.enabled=true. Required when registry.enabled=false. |
externalRegistry.username | "" | Username for an external registry (when registry.enabled=false) |
externalRegistry.password | "" | Password for an external registry |
backup.enabled | false | Deploy a nightly CronJob that backs up PostgreSQL to S3. Requires postgres.enabled=true. See Backups. |
backup.schedule | 0 3 * * * | Cron schedule for the backup Job |
backup.s3.uri | "" | Destination URI, e.g. s3://my-bucket/canette-backups. Required when backup.enabled=true. |
metrics.enabled | false | Unlock CPU/memory-over-time charts on the app Metrics tab. Pod health and instant usage work regardless of this setting. See Metrics. |
metrics.prometheus.bundled.enabled | false | Deploy a small in-chart Prometheus (no operator/CRDs) that scrapes kubelet cAdvisor cluster-wide. Ignored when externalUrl is set. |
metrics.prometheus.externalUrl | "" | BYO Prometheus-compatible query API (Prometheus, Thanos, Mimir, VictoriaMetrics). Takes precedence over the bundled instance. |
Gateway
| Value | Default | Description |
|---|---|---|
controller.gateway.name | can-gateway | Gateway resource name |
controller.gateway.namespace | kube-system | Gateway resource namespace |
BuildKit
| Value | Default | Description |
|---|---|---|
buildkit.rootless | true | Run buildkitd in rootless mode. Requires user namespace support on build nodes. Set to false on nodes that cannot support user namespaces (e.g. EKS Bottlerocket). |
buildkit.image | derived | Override the buildkitd image. When empty, defaults to moby/buildkit:v0.21.0-rootless when rootless=true, or moby/buildkit:v0.21.0 when rootless=false. |
buildkit.registries | {} | Additional registry trust entries for buildkitd. The in-cluster registry is added automatically. Format: "host:port": { http: true, insecure: true } |
buildkit.restrictEgress | true | Block RFC-1918 egress from the build namespace. Prevents a compromised build from reaching cluster-internal services while allowing internet access for package fetches. |
Registry authentication
In-cluster registry
Registry authentication is always enabled and auto-generated on first install. canette generates a random username and password, stores them in the canette-registry-credentials Secret, and reuses them on every helm upgrade — no configuration required.
Build jobs are automatically given a matching docker config Secret; no extra setup needed.
To use your own credentials instead (e.g. for disaster recovery or cluster migration):
registry:
username: myuser
password: mysecretIf you set custom credentials after auto-generated ones are already in use, existing push credentials in build jobs will be replaced. Rotate them in one upgrade to avoid authentication failures.
External registry
When registry.enabled=false, provide credentials for the external registry and set controller.pullRepo explicitly:
registry:
enabled: false
externalRegistry:
username: myuser
password: mytoken
builder:
imageRepo: ghcr.io/myorg/
controller:
pullRepo: ghcr.io/myorg/The host for the docker config is derived automatically from builder.imageRepo.
Amazon ECR with IRSA
For ECR on EKS, use IRSA (IAM Roles for Service Accounts) instead of static credentials. ECR repositories are automatically created on first build:
registry:
enabled: false
builder:
imageRepo: "123456.dkr.ecr.us-east-1.amazonaws.com/canette/"
registryAuthType: "irsa" # or omit to auto-detect from URL
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456:role/canette-builder
controller:
pullRepo: "123456.dkr.ecr.us-east-1.amazonaws.com/canette/"The registryAuthType value controls authentication:
irsa— Use ServiceAccount IAM role (EKS IRSA / GKE Workload Identity)static— Use username/password fromexternalRegistryvalues- Empty — Auto-detect:
irsafor ECR URLs,staticotherwise
See AWS (EKS) for IAM role setup.
Image scanning
Scanning is configured under security.scan and is applied at build time by the builder.
security:
scan:
provider: "auto" # "auto" | "trivy" | "ecr" | "none"
enabled: "" # "" = provider-aware default; use "true"/"false" (quoted strings)
mandatory: false # true blocks deployments when findings exceed failSeverity
failSeverity: "HIGH" # CRITICAL | HIGH | MEDIUM | LOW
sbomEnabled: false # true generates a CycloneDX SBOM (second Trivy pass, ~30–60 s extra)Provider selection
provider: "auto" (the default) picks the backend based on the builder.imageRepo URL:
| Registry | Provider | Enabled by default |
|---|---|---|
ECR (*.ecr.*.amazonaws.com) | ECR basic scanning | yes (free) |
| Everything else | Trivy K8s Job | no (opt-in) |
Override by setting provider explicitly, or flip the default with enabled: "true" or "false".
ECR scanning
The builder calls StartImageScan after each image push, then polls DescribeImageScanFindings until the scan completes (up to 10 minutes). No extra K8s Job is created. Results are stored as vulnerability counts — no CycloneDX SBOM is produced.
The builder's IAM role needs two additional permissions. See AWS (EKS) — ECR image scanning for the IAM policy.
Trivy scanning
When provider: "trivy" (or auto-selected for non-ECR registries), the builder creates a short-lived K8s Job in the build namespace that runs trivy image against the pushed image. The job produces vulnerability counts and, optionally, a CycloneDX SBOM available for download on the deployment detail page.
SBOM generation requires a second trivy image pass and adds roughly 30–60 seconds to scan time. It is disabled by default:
security:
scan:
sbomEnabled: trueOverride the Trivy image version:
builder:
trivyImage: "aquasec/trivy:0.58.0"Secret references
Several sensitive values can be sourced from existing Kubernetes Secrets rather than plain Helm values. Create the secret in the system namespace before running helm install or helm upgrade.
External database password
Put $(DB_PASSWORD) in the URL where the password goes, then point passwordSecretRef at your secret:
externalDatabase:
url: "postgresql://myuser:$(DB_PASSWORD)@myhost:5432/mydb?sslmode=require"
passwordSecretRef:
name: my-db-secret
key: passwordKubernetes substitutes $(DB_PASSWORD) at pod startup. Create the secret before running helm install or helm upgrade:
kubectl create secret generic my-db-secret \
--namespace canette-system \
--from-literal=password=<your-db-password>See Production setup for a full walkthrough of using an external database.
External database SSL
Managed databases (AWS RDS, Cloud SQL, Azure Database) use a certificate authority that is not in the default system trust store. Provide the CA certificate so canette can verify the server's TLS certificate.
Option 1 — reference an existing Secret (recommended):
Create a Secret containing the PEM-encoded CA bundle, then point caCertSecretRef at it:
kubectl create secret generic my-db-ca \
--namespace canette-system \
--from-file=ca.crt=rds-ca-bundle.pemexternalDatabase:
url: "postgresql://myuser:$(DB_PASSWORD)@mydb.abc123.us-east-1.rds.amazonaws.com:5432/mydb?sslmode=verify-full"
ssl:
caCertSecretRef:
name: my-db-ca
key: ca.crtOption 2 — inline PEM in values:
externalDatabase:
ssl:
caCert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----Do not use rejectUnauthorized: false in production. This disables certificate verification entirely, leaving the connection vulnerable to man-in-the-middle attacks. It exists only as an escape hatch for local dev or testing against managed cloud databases where you control the network.
externalDatabase:
ssl:
rejectUnauthorized: falseLast resort — disable SSL entirely. If the database server does not require SSL at all, you can turn it off via the connection string. This sends credentials and data in plaintext and must never be used in production.
externalDatabase:
url: "postgresql://myuser:mypassword@myhost:5432/mydb?sslmode=disable"No ssl values are needed when SSL is disabled in the connection string — sslmode=disable is handled entirely by the pg driver.
External registry password
externalRegistry:
username: myuser
passwordSecretRef:
name: my-registry-secret
key: passwordThe chart looks up the secret at render time to construct the docker config — the secret must exist before helm install.
GitHub client secret
api:
githubClientId: "my-client-id"
githubClientSecretRef:
name: my-github-secret
key: client-secretBackup S3 credentials
backup:
s3:
secretRef:
name: canette-backup-s3-credentials
accessKeyKey: access-key
secretKeyKey: secret-keySee Backups for the full setup, including the IAM policy.
Encryption key
The encryption key used to protect git credentials and app secrets at rest is auto-generated on first install and stored as a Kubernetes Secret. Every helm upgrade reuses the existing key automatically — no action required.
To provide your own key (disaster recovery, cluster migration):
encryptionKey: "your-32-character-key-here"Changing the encryption key after secrets have been stored will break decryption of all existing credentials and app secrets.
Upgrading
helm upgrade canette oci://ghcr.io/canette/canette --reuse-valuesTo upgrade to a specific version:
helm upgrade canette oci://ghcr.io/canette/canette --version 1.2.3 --reuse-valuesMigrations run automatically at API startup after the upgrade.
Uninstalling
helm uninstall canette -n canette-system
kubectl delete namespace canette-system canette-system-buildUninstalling deletes the BuildKit state volume and all in-cluster registry data. Back up any images you want to keep before uninstalling.
Operations
Rotating registry credentials
To rotate credentials without changing other values, use a gitignored secrets file:
# secrets.yaml (add to .gitignore)
externalRegistry:
username: myuser
password: mytokenhelm upgrade canette oci://ghcr.io/canette/canette --reuse-values -f secrets.yamlAfter upgrading, restart the builder to pick up the new credentials:
kubectl rollout restart deployment/canette-builder -n canette-systemUpdating resource defaults
# values.yaml
api:
defaultResources:
cpuRequest: "250m"
memoryRequest: "256Mi"
cpuLimit: "1"
memoryLimit: "1Gi"helm upgrade canette oci://ghcr.io/canette/canette --reuse-values -f values.yamlThe new values take effect on the next deployment for each app. Existing live apps continue to use the resource values from when they were last deployed.
Restarting a service
Rolling restart with zero downtime — the old pod stays up until the new one is ready:
kubectl rollout restart deployment/canette-builder -n canette-system
kubectl rollout restart deployment/canette-controller -n canette-system