Install on Kubernetes
Install a released Cryo with Helm, against your own PostgreSQL and Redis, with the dev token off and every image pinned by digest. Then sign in, add a source and run the first scan.
On this page
What you need#
- Kubernetes
- Version 1.27 or newer (the chart's
kubeVersionis>=1.27.0-0), with amd64 nodes. The published images are amd64-only, so every Cryo pod gets akubernetes.io/arch: amd64node selector. - Tools
- Helm 3,
kubectl,git,openssl, andcraneto look up image digests. - PostgreSQL
- An external database and a user that owns it. Cryo keeps its tables in a schema called
cryoin that database. The first migration runsCREATE EXTENSION IF NOT EXISTS pgcrypto, so either give the user that privilege or create the extension first.pgvectoris optional. Without it, the migration logs a warning and AI search falls back to a slower path. The local stack runs PostgreSQL 16. - Redis
- An external Redis for the job queue. Postgres is the source of truth, so Redis doesn't need persistence. You can use the chart's single in-namespace Redis instead (see Using the bundled Redis).
- An OIDC client
- A confidential client at your identity provider with redirect URI
https://<your-cryo-host>/login/callback. Its access tokens must carry a Cryo role. Security lists the exact claims. - Network
- Cryo's pods must be able to reach PostgreSQL, Redis, your catalogs, your engines (Trino or Spark), the object storage that holds the table files, and the OIDC issuer. Security has the full list.
Install#
Pick a version and get its chart
The chart isn't in a Helm repository. Take it from the git tag that matches the image tag, so the chart and the images come from the same release. This page uses
v0.1.5. Use the newestv*tag, and use it everywhere this page saysv0.1.5.terminal export CRYO_VERSION=v0.1.5 git clone --depth 1 --branch "$CRYO_VERSION" https://github.com/tech-pw/cryo.git "cryo-$CRYO_VERSION" cd "cryo-$CRYO_VERSION"Look up the image digests
The release images are public on GHCR:
ghcr.io/tech-pw/cryoruns the API, workers and migrations, andghcr.io/tech-pw/cryo-webis the UI. WithCRYO_ENVIRONMENT=prod, the chart won't render until every enabled image has asha256digest, so a moved tag can't change what runs.terminal export CRYO_DIGEST=$(crane digest "ghcr.io/tech-pw/cryo:$CRYO_VERSION") export CRYO_WEB_DIGEST=$(crane digest "ghcr.io/tech-pw/cryo-web:$CRYO_VERSION") echo "$CRYO_DIGEST $CRYO_WEB_DIGEST"Done when both values start with
sha256:.Write the settings file
Every Cryo process reads its
CRYO_*settings from the Secret named byglobal.envFromSecret, and that includes the migration Job. Save the settings below ascryo.envand replace every placeholder. Keeping them in a file keeps them out of your shell history.cryo.env CRYO_DATABASE_URL=postgresql+psycopg://cryo:REPLACE_ME@postgres.example.internal:5432/cryo CRYO_REDIS_URL=redis://redis.example.internal:6379/0 CRYO_OIDC_ISSUER_URL=https://idp.example.com/realms/data CRYO_OIDC_CLIENT_ID=cryo CRYO_OIDC_CLIENT_SECRET=REPLACE_ME CRYO_OIDC_REDIRECT_URI=https://cryo.example.com/login/callbackCRYO_OIDC_ISSUER_URLmust match theissclaim in your tokens. If your provider's access tokens use an audience other than the client ID, addCRYO_OIDC_AUDIENCEas well.Create the namespace and the Secret
Add two random keys to the file: one encrypts stored secrets, the other signs Cryo's own tokens. Then load the file into the Secret and delete it.
terminal echo "CRYO_SECRET_KEY=$(openssl rand -hex 32)" >> cryo.env echo "CRYO_JWT_SECRET=$(openssl rand -hex 32)" >> cryo.env kubectl create namespace cryo kubectl -n cryo create secret generic cryo-secrets --from-env-file=cryo.env rm cryo.envKeep
CRYO_SECRET_KEY. It also encrypts the connection secrets Cryo stores, so back it up alongside the database. If you lose it or change it, those stored secrets can't be read. See Security.Write the values file
Save this as
cryo-values.yaml. It setsprod, takes both images from the release, turns on the UI and points every process at the Secret. The bundled Redis is off becauseCRYO_REDIS_URLpoints at your own. The digests are passed on the command line in the next step.cryo-values.yaml global: envFromSecret: cryo-secrets env: CRYO_ENVIRONMENT: prod image: repository: ghcr.io/tech-pw/cryo tag: v0.1.5 migration: image: repository: ghcr.io/tech-pw/cryo tag: v0.1.5 web: enabled: true image: repository: ghcr.io/tech-pw/cryo-web tag: v0.1.5 redis: enabled: falseThe native and Spark-on-Kubernetes lanes are off by default. If you turn on
native.enabledorspark.k8s.enabled, pin those images by digest too.deploy/helm/cryo/values.yamldocuments every other setting and its default.Render, then install
Render first. A missing digest or a typo fails at this point, before anything touches the cluster.
terminal helm template cryo ./deploy/helm/cryo --namespace cryo -f cryo-values.yaml --set image.digest="$CRYO_DIGEST" --set migration.image.digest="$CRYO_DIGEST" --set web.image.digest="$CRYO_WEB_DIGEST" > /dev/null helm upgrade --install cryo ./deploy/helm/cryo --namespace cryo -f cryo-values.yaml --set image.digest="$CRYO_DIGEST" --set migration.image.digest="$CRYO_DIGEST" --set web.image.digest="$CRYO_WEB_DIGEST" --wait --timeout 15mDone when Helm prints
STATUS: deployed. With the release namecryo, the Services arecryo-apion port 8000 andcryo-webon port 80.
How migrations run#
Every helm install and helm upgrade starts with a hook Job called cryo-migrate-<revision>. It runs alembic upgrade head with the same image and Secret. The release waits for the Job, and no Deployment changes until it succeeds. Helm deletes the Job when it succeeds and keeps it when it fails, so after a failed install you can still read the log:
kubectl -n cryo get jobs -l app.kubernetes.io/component=migration
kubectl -n cryo logs job/cryo-migrate-1After a successful install, check which revision the database is on:
kubectl -n cryo exec deploy/cryo-api -- alembic currentDone when it prints one revision ending in (head).
Check the install#
Pods are ready
terminal kubectl -n cryo get podsDone when the api, worker, governor, scheduler and web pods are all
Runningand ready. The scanner worker can stay at zero replicas until there's scan work.The API answers and the dev token is off
In a second terminal, run
kubectl -n cryo port-forward svc/cryo-api 8000:8000./healthzonly shows that the process is up./healthz/deepchecks PostgreSQL and Redis as well.terminal curl -s localhost:8000/healthz curl -s localhost:8000/healthz/deep curl -s localhost:8000/api/v1/auth/config curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8000/api/v1/auth/token -H 'content-type: application/json' -d '{"role":"admin"}'Done when
/healthzreports"environment":"prod"and/healthz/deepreturns"status":"ok". The auth config should show"mode":"oidc"and"dev_token_enabled":false, and the token request should print503.Expose the UI
The chart has no Ingress. Point your own Ingress or load balancer at the
cryo-webService on port 80, using the host fromCRYO_OIDC_REDIRECT_URI. The web pod serves the UI and forwards/api/to the API, so you only need one host. Ask Cryo streams server-sent events, so turn proxy buffering off and allow a read timeout of at least 60 seconds.cryo-ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: cryo namespace: cryo annotations: nginx.ingress.kubernetes.io/proxy-buffering: "off" nginx.ingress.kubernetes.io/proxy-read-timeout: "60" spec: ingressClassName: nginx tls: - hosts: [cryo.example.com] secretName: cryo-tls rules: - host: cryo.example.com http: paths: - path: / pathType: Prefix backend: service: name: cryo-web port: number: 80These annotations are for ingress-nginx, so use your controller's equivalents if you run a different one. The web pod does not forward
/metricsor/healthz/deep: it answers both with 404, and admins read them in the UI through/api/v1/ops/metricsand/api/v1/ops/healthz/deep. Prometheus and probes reach the API Service directly. SetCRYO_METRICS_BEARER_TOKENto require a token on the API's/metrics.Sign in
Open
https://cryo.example.comand sign in through your identity provider. You need theadminrole in your access token to add sources. If/api/v1/auth/configreturns 503 withOIDC issuer discovery failed, the API can't reachCRYO_OIDC_ISSUER_URL/.well-known/openid-configuration.Done when Home loads. It stays empty until the first scan.
Add a source and run the first scan
Follow Connect a lakehouse to add a catalog and start a scan. The scanner CronJob also scans every source every six hours, as set by
scanner.schedule.Done when Tables lists your tables with grades.
Using the bundled Redis#
When redis.enabled is true, which is the chart default, the chart runs one Redis pod with no volume behind a ClusterIP Service called cryo-redis. The chart doesn't set CRYO_REDIS_URL for you, so put CRYO_REDIS_URL=redis://cryo-redis:6379/0 in the Secret. In prod, the Redis image needs a digest too:
export REDIS_DIGEST=$(crane digest redis:7-alpine)
helm upgrade --install cryo ./deploy/helm/cryo --namespace cryo -f cryo-values.yaml --set image.digest="$CRYO_DIGEST" --set migration.image.digest="$CRYO_DIGEST" --set web.image.digest="$CRYO_WEB_DIGEST" --set redis.enabled=true --set redis.image.digest="$REDIS_DIGEST" --wait --timeout 15mUpgrade#
Back up the database first. Then repeat the install with the new tag: check out its chart, look up its digests, change the three tag values in cryo-values.yaml, render, and run helm upgrade --install. The migration hook runs before any pod changes. Upgrade and rollback has the details and the limits of a rollback.
Uninstall#
helm uninstall cryo --namespace cryoThis removes the Deployments, Services, CronJobs, ServiceAccount and RBAC that the release created. These are left behind:
- The database. The
cryoschema stays in PostgreSQL, with its run history, audit rows and stored connections. Drop it yourself if you want them gone. - Redis keys in your external Redis. The bundled Redis has no volume, so its data goes when its pod does.
- The Secret
cryo-secretsand the namespace, because you created them outside the chart. - Runtime Jobs started by the native or Spark lanes. They aren't part of the release and are removed through
ttlSecondsAfterFinished. Check withkubectl -n cryo get jobs,pods. - Changes to your tables. Maintenance that already ran is permanent. Compacted files and expired snapshots don't come back.