CI/CD: build, scan, sign, push, deploy¶
Two workflows own delivery. Every action in them is pinned by commit SHA.
deploy-verify.yml (no cloud account needed)¶
Runs on any change to deploy/, the Dockerfiles or the workflow itself.
| Job | What it proves |
|---|---|
helm |
helm lint --strict and kubeconform -strict (Kubernetes 1.29 and 1.31 schemas) for every profile, rendered with every optional component on |
terraform |
terraform fmt -check, validate and an offline plan with the example variables, for the AWS module, both AWS examples and the GCP module |
kind |
builds the three images, checks their security properties (deploy/docker/verify_image.sh), installs the chart on a throwaway kind cluster (CPU profile), runs helm test, then signs up an organisation and makes calls through the real services |
images.yml (build, scan, sign, push, deploy)¶
pull request ──► build ─► scan ─► SBOM (nothing pushed)
push to main ──► build ─► scan ─► SBOM ─► push ─► sign + attest ─► verify ─► deploy staging
tag v* ──► build ─► scan ─► SBOM ─► push ─► sign + attest ─► verify ─► deploy staging ─► [approval] ─► deploy production
- Images:
tycheon-api,tycheon-cloud(CPU),tycheon-cloud-cuda(GPU workers),tycheon-dashboardatghcr.io/<owner>/. Tags aresha-<12 hex>,main, and the release version; deploys use the digest the pipeline just signed, never a tag. - Scan: Trivy fails the build on any HIGH or CRITICAL vulnerability that has a fix. Findings with no available fix (mostly in the Debian base image) are reported, not blocking: see the launch checklist for the current list.
- SBOM: SPDX from Syft, uploaded as an artifact and attached to the image as a signed attestation.
- Signing:
cosign signkeyless with the workflow's OIDC identity: no key is stored. The pipeline immediately verifies the signature against that identity. To verify an image yourself:cosign verify ghcr.io/<owner>/tycheon-api@sha256:... \ --certificate-identity-regexp '^https://github.com/<owner>/tycheon/\.github/workflows/images\.yml@' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com - Staging deploys automatically from
main(Helm--atomic: a failed upgrade rolls itself back), thenhelm testand an HTTP smoke test. - Production deploys only from a
v*tag, only after staging succeeded, and only after a reviewer approves theproductionenvironment. It takes a database snapshot first, deploys atomically, smoke-tests, and rolls back the release if the smoke test fails.
Turning the deploys on¶
Both deploy jobs are skipped, not failed, until configured, because there is no cluster yet:
| Setting | Where | For |
|---|---|---|
STAGING_ENABLED=true, STAGING_URL, STAGING_DASHBOARD_URL |
repository variables | staging |
STAGING_KUBECONFIG (base64) |
staging environment secret |
staging |
PRODUCTION_ENABLED=true, PRODUCTION_URL |
repository variables | production |
PRODUCTION_KUBECONFIG (base64) |
production environment secret |
production |
PRODUCTION_RDS_INSTANCE, PRODUCTION_AWS_ROLE_ARN, PRODUCTION_AWS_REGION |
repository variables | pre-deploy snapshot (OIDC, no stored AWS keys) |
| Required reviewers | production environment settings |
the manual approval |
Prefer short-lived cluster credentials (an OIDC role that can only helm upgrade in one namespace)
over a long-lived kubeconfig.
Migrations and rollback¶
The Helm release runs python -m tycheon_cp.cli migrate as a pre-upgrade Job under the owner
database role; the running application uses an unprivileged role. A failed migration stops the
release before any new pod starts. Each migration has a tested .down.sql; python -m tycheon_cp.cli rollback
runs them, refusing any that drops tables without --allow-data-loss. See
bad migration. This replaces Alembic: the migrations are hand-written SQL
(row-level security and SECURITY DEFINER functions are awkward to express through an ORM), so there
is no new dependency.
Rolling back a release¶
helm rollback tycheon -n tycheon --wait restores the previous Deployments. It does not undo the
schema; migrations are written to be additive so the previous release keeps working.