Install on Kubernetes with Helm
Operators running Kubernetes who want to deploy TRUSCA with the
production-grade Helm chart. Assume kubectl, Helm 3, and basic cluster
administration (Ingress, StorageClasses, cert-manager) proficiency. If you run a
single host, the Docker Compose install is simpler.
The Helm chart (charts/trustedoss, chart version 0.10.0) deploys the full
portal: the FastAPI backend, the Celery worker and beat scheduler, the React
frontend, an Ingress with TLS, and a database migration Job. PostgreSQL and
Redis can either be bundled in-cluster (for evaluation) or pointed at external
managed datastores (recommended for production).
The worker pod ships with the Trivy DB and downloads / refreshes it from ghcr.io/aquasecurity/trivy-db (or a mirror via env.trivy.dbRepository). No external vulnerability engine is required. See Vulnerability data (Trivy DB).
What the chart deploys
| Workload | Kind | Notes |
|---|---|---|
| backend | Deployment | FastAPI API. AUTO_MIGRATE=false — migrations run in the Job. |
| worker | Deployment (+ optional HPA) | Celery worker (cdxgen / scancode / Trivy). |
| beat | Deployment (replicas: 1) | Celery scheduler — singleton. |
| frontend | Deployment | React SPA on nginx (:8080). |
| postgres | StatefulSet | Optional bundle (postgres.bundled). |
| redis | Deployment | Optional bundle (redis.bundled). |
| migrate | Job (pre-install / pre-upgrade hook) | alembic upgrade head as the owner role. |
| ingress | Ingress | cert-manager TLS; API + SPA routing. |
Prerequisites
- A Kubernetes cluster and a
kubectlcontext with permission to create the namespace and workloads. - Helm 3.
- An ingress controller (the chart defaults to class
nginx). - cert-manager with a
ClusterIssuernamedletsencrypt-prodfor the default TLS configuration (override viaingress.annotations). - On multi-node clusters, a
ReadWriteManyStorageClass for the shared scan workspace (workspace.persistence.storageClassName). A single-node cluster can use the per-podemptyDirfallback.
Validate the chart before installing
Before deploying, render the in-repo chart locally to catch values / template errors without touching a cluster (Helm 3+, from the repository root):
SECRET=$(openssl rand -hex 32)
helm lint charts/trustedoss \
--set env.secret.secretKey="$SECRET" \
--set postgres.auth.password=throwaway \
--set ingress.host=trustedoss.example.com
helm template trustedoss charts/trustedoss --namespace trustedoss \
--set env.secret.secretKey="$SECRET" \
--set postgres.auth.password=throwaway \
--set ingress.host=trustedoss.example.com \
>/dev/null
helm lint reports chart-structure problems; helm template fully renders
every manifest with the minimum required values, so a non-zero exit means the
chart would not install. The --set values here are throwaway — the real
install below uses your own secrets.
Quick start (bundled datastores, evaluation)
This runs PostgreSQL and Redis in-cluster — fast to stand up, but not recommended for production data.
helm install trustedoss oci://ghcr.io/trustedoss/charts/trustedoss \
--version 0.10.0 \
--namespace trustedoss --create-namespace \
--set env.secret.secretKey="$(openssl rand -hex 32)" \
--set postgres.auth.password="$(openssl rand -hex 24)" \
--set ingress.host=trustedoss.example.com \
--set env.corsAllowedOrigins=https://trustedoss.example.com
Replace trustedoss.example.com with your own hostname, and make sure DNS for
that host points at your ingress controller.
The in-cluster PostgreSQL and Redis have modest defaults and a single replica. For anything beyond a trial, use external managed datastores (below).
Production (external managed datastores — recommended)
Prefer Cloud SQL / RDS for PostgreSQL and Memorystore / ElastiCache for Redis over the in-cluster bundles. Provide a values file:
# values.prod.yaml
postgres:
bundled: false
redis:
bundled: false
env:
database:
url: postgresql+asyncpg://app:***@cloudsql-proxy:5432/trustedoss
# if you separate the DDL/owner role from the runtime role:
ownerUrl: postgresql+asyncpg://owner:***@cloudsql-proxy:5432/trustedoss
redis:
url: redis://memorystore:6379/0
secret:
# pre-created Secret carrying all four keys (see below)
existingSecret: trustedoss-prod-secrets
corsAllowedOrigins: https://trustedoss.example.com
ingress:
host: trustedoss.example.com
Then install:
helm install trustedoss oci://ghcr.io/trustedoss/charts/trustedoss \
--version 0.10.0 \
--namespace trustedoss --create-namespace \
-f values.prod.yaml
When env.secret.existingSecret is set, the chart renders no Secret of its
own. The referenced Secret must carry all four keys, or the pods will not
start:
DATABASE_URL_APPDATABASE_URL_OWNERREDIS_URLSECRET_KEY(at least 32 characters)
env.corsAllowedOrigins must enumerate the exact origins that serve the SPA
— no wildcard in production. List every scheme + host that browsers will use.
How migrations run
A Helm pre-install + pre-upgrade hook Job runs alembic upgrade head once
as the owner DB role (DATABASE_URL_OWNER). The application pods run with
AUTO_MIGRATE=false, so the Job is the sole migrator.
Backend pods stay NotReady (/health/ready returns 503) until the schema is
at HEAD, so traffic only ever reaches a migrated schema. Migrations are
forward-only — the Job never downgrades. Hook ordering for the bundled case is:
Secrets → Postgres Service / StatefulSet → migration Job, and the Job's init
container waits for Postgres to accept connections before alembic runs.
Upgrade
helm upgrade trustedoss oci://ghcr.io/trustedoss/charts/trustedoss \
--version <new-chart-version> \
--namespace trustedoss \
-f values.prod.yaml
The pre-upgrade migration Job applies any new schema before the new pods roll out. Because migrations are forward-only, take a database backup before upgrading — see Backup & restore.
Key values
The full table lives in the chart README. The values you most often set:
| Key | Default | Purpose |
|---|---|---|
image.tag | 0.10.0 | Image tag for backend / worker / frontend (never :latest). |
ingress.host | "" | Required. Public hostname. |
env.corsAllowedOrigins | "" | Required in prod. Allowed browser origins (no wildcard). |
env.secret.secretKey | "" | SECRET_KEY (≥32 chars). Required unless existingSecret. |
env.secret.existingSecret | "" | Pre-created Secret with all four keys; disables the chart Secret. |
postgres.bundled | true | false → use env.database.* (external). |
redis.bundled | true | false → use env.redis.url (external). |
env.trivy.dbRepository | ghcr.io/aquasecurity/trivy-db | Override for an air-gapped internal mirror — see Air-gapped operation. |
env.trivy.dbRefreshHours | 168 | Weekly Trivy DB refresh; lower for fresher feeds. |
worker.trivyDbPersistence.enabled | true | Mount a PVC at /var/lib/trivy so the worker doesn't re-download on every restart. |
workspace.persistence.storageClassName | "" | RWX class for the shared scan volume on multi-node clusters. |
worker.replicaCount | 2 | Prefer scaling worker pods over per-pod concurrency. |
Verify it worked
-
The migration Job completed:
kubectl -n trustedoss get jobs# the trustedoss migrate Job should show COMPLETIONS 1/1
-
All pods are
Runningand backend pods areReady:kubectl -n trustedoss get pods# backend pods Ready means /health/ready returned 200 (schema at HEAD)
-
The readiness probe passes from inside the cluster:
kubectl -n trustedoss exec deploy/trustedoss-backend -- \curl -fsS http://localhost:8000/health/ready# → {"status":"ready"}
- The Ingress has an address and a valid certificate, then open
https://<ingress.host>/in a browser and sign in.
Troubleshooting
-
Backend pods stuck
NotReady./health/readyreturns503until the schema is at HEAD. Check the migration Job logs:kubectl -n trustedoss logs job/trustedoss-migrateA failed Job usually means the owner DSN (
DATABASE_URL_OWNER) lacks DDL privileges or cannot reach the database. -
Pods
CreateContainerConfigErrorwith an existing Secret. The referenced Secret is missing one of the four required keys. Confirm:kubectl -n trustedoss get secret trustedoss-prod-secrets -o jsonpath='{.data}' | tr ',' '\n'# expect DATABASE_URL_APP, DATABASE_URL_OWNER, REDIS_URL, SECRET_KEY -
Scans fail on multi-node clusters. The backend and worker share the scan workspace. Without a
ReadWriteManyStorageClass the worker cannot read what the backend wrote. Setworkspace.persistence.storageClassNameto an RWX class (nfs / efs / filestore / longhorn). -
TLS certificate never issues. The default annotations expect a cert-manager
ClusterIssuernamedletsencrypt-prod. Inspect the Certificate:kubectl -n trustedoss describe certificate
If you hit a chart bug, open an issue using the bug report template.
See also
- Install with Docker Compose — single-host install
- Upgrade — Docker Compose upgrade path
- Backup & restore — back up before upgrading
- Environment variables — every setting the chart maps
- Architecture — services, Trivy DB lifecycle, and the migration model
- Vulnerability data (Trivy DB) — air-gapped operation and DB refresh
- v0.10.0 release notes — chart 0.10.0 breaking changes