Docs · reference
Self-host deployment guide — install Driftlock inside your VPC, in front of a security reviewer.
Nine prerequisites the platform team holds (Docker · Postgres · S3-compatible object storage · VPC · KMS · OIDC · registry · GitHub · egress), a docker-compose walkthrough of every service the chart ships, the full Helm values + templates inventory, the env-var reference table cross-referenced against .env.example and the runtime schema, nginx / Caddy / Envoy reverse-proxy + TLS snippets, a one-page upgrade runbook, the Helm-managed DB migration step (with pg_verifybackup + a PITR + rollback tactic), three overnight schedules (02:14 UTC scan · 03:00 UTC sbom-reaper · 04:00 UTC Sun rekor-prune), and a .driftlock/config.yaml RBAC bootstrap snippet mapped to the six roles × eleven actions grant table a reviewer reads on /docs/compliance#rbac-matrix.
Nine sections
Prerequisites → RBAC bootstrap
Nine prerequisites · seven compose services + a starter snippet · twelve Helm values + seven templates · seventeen env vars · three reverse-proxy snippets · seven upgrade runbook steps · four DB migration hooks · three overnight cron schedules · a .driftlock/config.yaml RBAC bootstrap snippet — every anchor deep-linkable from the right rail.
Vocabulary reuse
Same keys as the audit trail
sandbox.cpu · sandbox.memory · egress.allowed · dns.policy · tls.pinningRequired — same knobs the sandbox deep-dive documents, mirrored in this guide.
Source of truth
Env vars + RBAC cross-referenced
Every env var this page cites is grounded in .env.example, the env.ts schema, or chart values — secrets are hedged as KMS-injected. The RBAC bootstrap snippet is the same six × eleven matrix /docs/compliance#rbac-matrix renders, restated as a paste-ready file.
prerequisites · docker-compose · helm-chart · env-vars · reverse-proxy-tls · upgrade-runbook · db-migration · overnight-cron · rbac-bootstrap
Prerequisites
What the platform team holds before the first helm install.
Seven rows the platform team confirms before the install. Each row names the requirement, the verification the platform team runs to greenlight install, and the compliance control the prerequisite maps to. Reviewers read this table side by side with the chat "prerequisites" row the parent /docs page surfaces — same vocabulary, expanded to the items the runbook actually verifies.
Prerequisite · anchor
Requirement · verification
VPC layout
anchor · SOC 2 CC6.6 · FedRAMP AC-4
One private subnet per environment, outbound deny-by-default, delegated KMS / HSM role, and a /16 prefix reserved for watcher / sandbox / signing-controller / webhook-receiver / sbom-emitter.
- Verification
- Run `aws ec2 describe-vpc-endpoints` (or equivalent) and confirm the endpoint set has KMS, Secrets Manager, ECR / Artifact Registry, and the cluster control plane only — no public subnets touching the workload tier.
KMS / HSM target
anchor · SOC 2 CC6.1 · FedRAMP SC-12
AWS KMS · GCP KMS · Azure Key Vault, or on-prem HSM via PKCS#11. Two distinct keys: one for the cosign commit signer, one for the SBOM emitter. Enterprise tier wires both directly to your HSM.
- Verification
- Run `aws kms list-aliases` and confirm `driftlock/commit-signer` + `driftlock/sbom-signer` with key-policy rotation set to ≤ 90 days. For HSM, run the PKCS#11 round-trip test the chart ships under `tools/pkcs11-smoke`.
OIDC IdP
anchor · SOC 2 CC6.1 · FedRAMP IA-2
Okta · Entra · Google, or your existing internal IdP. Issues the OIDC claims the verifier checks (cosign, Rekor, dependency feed pulls). No long-lived shared secrets in watcher or sandbox.
- Verification
- Run `curl -fsS OIDC_ISSUER/.well-known/openid-configuration` (substituting the chart-injected issuer URL) and confirm the JWKS endpoint is reachable from the sandbox namespace via the egress allow-list.
Container registry
anchor · SOC 2 CC6.7 · FedRAMP SI-7
Any OCI-compatible registry with cosign-pinned pulls. Optional mirror for dual-region installs so the watcher pulls from the closest warm registry. Signatures verified at pull time.
- Verification
- Run `cosign verify --certificate-identity-regexp ".*" registry.internal/driftlock/watcher:vX.Y.Z` and confirm exit 0 with the Rekor uuid recorded in the cluster-side audit log.
Postgres target
anchor · SOC 2 CC7.2 · FedRAMP AU-2
PostgreSQL 16+ · 8 vCPU · 32 GiB · gp3 storage · automated base backup + PITR. Prisma is the framework-native client; no schema migrations outside the Helm-managed install job.
- Verification
- Run `psql "${DATABASE_URL}" -c "SELECT version();"` and confirm version ≥ 16,`pg_basebackup` succeeds against the WAL archive, and PITR can replay at least 24h back.
GitHub org + App scope
anchor · SOC 2 CC6.1 · FedRAMP AC-6
A GitHub org with the Driftlock App installed on the 2-10 repos your trial covers. Permissions scoped at install from the App manifest — least-privilege, revocable per repo, no org-wide grants.
- Verification
- Run `gh api /repos/<org>/<repo>/installation` and confirm permissions = read:contents, write:issues, write:pulls, read:checks (no admin:org, no delete:*).
Egress allow-list
anchor · SOC 2 CC6.6 · FedRAMP AC-4
Default-deny NetworkPolicy on the sandbox namespace. Every outbound host enumerated in `driftlock.egress.allowed` and reviewed at `helm upgrade` time. KMS / OIDC / registry / Rekor / GitHub / cosign are the only hosts in the allow-list on day one.
- Verification
- Run `kubectl exec -n driftlock deploy/sandbox-runner -- nc -zv unknown-binary.mirror.example 443` and confirm TIMEOUT (block), then run the same against each allow-list CIDR and confirm OPEN.
Docker engine
anchor · SOC 2 CC6.7 · FedRAMP CM-7
Docker Engine 24.0+ with BuildKit enabled and the `containerd` snapshotter on every node in the platform team — or, on Enterprise, a hardened `nerdctl` / Podman 4.7+ equivalent gated by the same SELinux / AppArmor profile the cluster enforces. The chart pins Compose Spec 3.8; the marketplace images the runbook pulls all declare `OCI image media type: application/vnd.docker.distribution.manifest.v2+json`.
- Verification
- Run `docker version --format "{{.Server.Version}}"` and confirm ≥ 24.0.0, `docker info --format "{{.Features.BuildKit}}"` returns `true`, and a dry-run `docker compose config` against the starter snippet in this guide renders without warning.
S3-compatible object storage
anchor · SOC 2 CC6.1 · FedRAMP AU-2 · FedRAMP SC-28
An S3-compatible bucket the sbom-emitter + audit-trail writer reach over TLS — AWS S3, Cloudflare R2, MinIO ≥ RELEASE.2023-10-07T, or on-prem Ceph RGW. SSE-KMS on Team and Platform, customer-managed CMK + object-lock on Enterprise. Lifecycle rule pins the SBOM delta against the PR hash for ≥ 90 days; audit-trail rows are kept 7y hot + 10y cold.
- Verification
- Run `aws s3api head-bucket --bucket driftlock-sbom` (or the R2 / MinIO `mc stat driftlock/driftlock-sbom` equivalent) and confirm the policy returns SSE-KMS with the chart-injected KMS alias, an object-lock retention window ≥ 90d on the SBOM prefix, and a lifecycle rule that transitions `audit-trail/*` to GLACIER after the 7y hot tier elapses.
vpc · kms / hsm · oidc · registry · postgres · github app · egress allow-list · docker · object storage
Docker-compose walkthrough
Seven services, a labelled diagram, and a starter the platform team commits.
The watcher + webhook-receiver + sandbox-runner + sbom-emitter + signing-controller + db + optional redis make up the Driftlock compose graph. Each card below names the image tag the platform team pins, the volumes, the environment set, the depends_on chain, the ports (or lack thereof — every workload is cluster-only), and the healthcheck the platform team commits alongside the chart.
Starter docker-compose.yml the platform team commits to their chart repo
The snippet below is illustrative — the platform team commits the actual file to their chart repo and reviews the diff at every helm upgrade. The platform team diffs the snippet against their last review on every chart bump; nothing in this block is vendored.
# docker-compose.yml — starter the platform team commits to
# their chart repo. This file is illustrative — the platform team
# reviews the diff at every helm upgrade and treats the snippet below as
# the baseline rather than a vendored copy.
#
# Pin every image by tag, not :latest, so the hermetic-build hash stays
# byte-stable across reruns. Image signatures are verified at pull time
# against the OIDC issuer under driftlock.oidc.issuer.
x-driftlock-image: &driftlock-image
image: registry.internal/driftlock/{name}:vX.Y.Z # pin per service
restart: unless-stopped
networks: [driftlock]
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
services:
watcher:
<<: *driftlock-image
image: registry.internal/driftlock/watcher:vX.Y.Z
environment:
DATABASE_URL: ${DATABASE_URL}
BETTER_AUTH_URL: ${BETTER_AUTH_URL}
DRIFTLOCK_KMS_KEY_ALIAS: driftlock/commit-signer
volumes:
- ./certs/watcher.pem:/etc/driftlock/tls.pem:ro
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
depends_on: { db: { condition: service_healthy } }
webhook-receiver:
<<: *driftlock-image
image: registry.internal/driftlock/webhook-receiver:vX.Y.Z
environment:
DATABASE_URL: ${DATABASE_URL}
DRIFTLOCK_WEBHOOK_SHARED_SECRET_ALIAS: driftlock/webhook-hmac
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
depends_on: { watcher: { condition: service_healthy } }
sandbox-runner:
<<: *driftlock-image
image: registry.internal/driftlock/sandbox-runner:vX.Y.Z
environment:
DATABASE_URL: ${DATABASE_URL}
DRIFTLOCK_HERMETIC: "1"
DRIFTLOCK_EGRESS_ALLOWED: ${DRIFTLOCK_EGRESS_ALLOWED}
volumes:
- runner-cache:/var/cache/driftlock
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
depends_on: { db: { condition: service_healthy } }
sbom-emitter:
<<: *driftlock-image
image: registry.internal/driftlock/sbom-emitter:vX.Y.Z
environment:
DATABASE_URL: ${DATABASE_URL}
DRIFTLOCK_SBOM_KEY_ALIAS: driftlock/sbom-signer
DRIFTLOCK_REKOR_URL: ${DRIFTLOCK_REKOR_URL:-https://rekor.driftlock.dev}
volumes:
- sbom-out:/var/lib/driftlock/sbom
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
depends_on: { sandbox-runner: { condition: service_healthy } }
signing-controller:
<<: *driftlock-image
image: registry.internal/driftlock/signing-controller:vX.Y.Z
environment:
DATABASE_URL: ${DATABASE_URL}
DRIFTLOCK_COMMIT_KEY_ALIAS: driftlock/commit-signer
DRIFTLOCK_OIDC_ISSUER: ${DRIFTLOCK_OIDC_ISSUER}
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
depends_on:
sandbox-runner: { condition: service_healthy }
sbom-emitter: { condition: service_healthy }
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: driftlock
POSTGRES_USER: driftlock
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
networks: [driftlock]
# Optional — drop when the platform team prefers Postgres for both
# HMAC replay-window and dedupe caching.
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks: [driftlock]
networks:
driftlock:
driver: bridge
internal: true # no egress by default — rely on egress.allowed at the chart level
volumes:
pgdata:
redisdata:
runner-cache:
sbom-out:
Compose service
watcher
Receives outbound webhook deliveries from the GitHub App and CI runner; correlates alert / advisory / log triggers against the PR-review surface.
- image
- registry.internal/driftlock/watcher:vX.Y.Z
- volumes
- ./certs/watcher.pem:/etc/driftlock/tls.pem:ro
- environment
- DATABASE_URLBETTER_AUTH_URLDRIFTLOCK_KMS_KEY_ALIAS
- depends_on
- db
- ports
- cluster-only — no public exposure
- healthcheck
- wget --quiet --tries=1 --spider http://localhost:8080/healthz || exit 1
Compose service
webhook-receiver
In-VPC endpoint the GitHub App and the CI runner post to. Verifies HMAC-SHA256 over the raw request body before the payload lands in the watcher.
- image
- registry.internal/driftlock/webhook-receiver:vX.Y.Z
- environment
- DATABASE_URLDRIFTLOCK_WEBHOOK_SHARED_SECRET_ALIAS
- depends_on
- watcher
- ports
- cluster-only — no public exposure
- healthcheck
- wget --quiet --tries=1 --spider http://localhost:8080/healthz || exit 1
Compose service
sandbox-runner
Owns the hermetic-build sandbox step. Fans out `run · replay · scan` and emits the test-results report the runner reads back into the audit-trail row.
- image
- registry.internal/driftlock/sandbox-runner:vX.Y.Z
- volumes
- runner-cache:/var/cache/driftlock
- environment
- DATABASE_URLDRIFTLOCK_HERMETICDRIFTLOCK_EGRESS_ALLOWED
- depends_on
- db
- ports
- cluster-only — no public exposure
- healthcheck
- wget --quiet --tries=1 --spider http://localhost:8080/healthz || exit 1
Compose service
sbom-emitter
Emits the CycloneDX 1.5 document for every successful run, signs it with the SBOM-resident KMS key, and writes the inclusion claim to Rekor.
- image
- registry.internal/driftlock/sbom-emitter:vX.Y.Z
- volumes
- sbom-out:/var/lib/driftlock/sbom
- environment
- DATABASE_URLDRIFTLOCK_SBOM_KEY_ALIASDRIFTLOCK_REKOR_URL
- depends_on
- sandbox-runner
- ports
- cluster-only — no public exposure
- healthcheck
- wget --quiet --tries=1 --spider http://localhost:8080/healthz || exit 1
Compose service
signing-controller
Reads the cosign commitment from the sbom-emitter, fetches a fresh OIDC token, and stamps the Driftlock-attributed commit. Records signer_kid + Rekor uuid on the audit-trail row.
- image
- registry.internal/driftlock/signing-controller:vX.Y.Z
- environment
- DATABASE_URLDRIFTLOCK_COMMIT_KEY_ALIASDRIFTLOCK_OIDC_ISSUER
- depends_on
- sandbox-runnersbom-emitter
- ports
- cluster-only — no public exposure
- healthcheck
- wget --quiet --tries=1 --spider http://localhost:8080/healthz || exit 1
Compose service
db
Single-node Postgres target the install job bootstraps before any workload rolls. Production deploys swap this for a managed Postgres + PITR; the compose target is intended for first-install / air-gap drills.
- image
- postgres:16-alpine
- volumes
- pgdata:/var/lib/postgresql/data
- environment
- POSTGRES_DBPOSTGRES_USERPOSTGRES_PASSWORD
- depends_on
- —
- ports
- cluster-only — no public exposure
- healthcheck
- pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}
Compose service
redis
Optional — used by the webhook-receiver for HMAC replay-window caching and by the sandbox-runner for the dedupe table. Drop the service when the platform team prefers to scale Postgres for both.
- image
- redis:7-alpine
- volumes
- redisdata:/data
- environment
- depends_on
- —
- ports
- cluster-only — no public exposure
- healthcheck
- redis-cli ping | grep -q PONG
watcher · webhook-receiver · sandbox-runner · sbom-emitter · signing-controller · db · redis (opt)
Helm chart walkthrough
Values, templates, install / upgrade commands — reviewable end to end.
The chart ships one values.yaml the platform team owns. Twelve keys below are the ones the runbook actually changes; every key carries a default, a type, a description, and the compliance control it maps to. Seven templates — deployment · service · secret · ingress · serviceaccount · hpa · networkpolicy — make up the manifest set the cluster receives.
Install + upgrade commands the runbook runs
Render first with helm template, diff against the previous revision, then helm install --atomic. The same loop drives helm upgrade and helm rollback.
# Refresh the helm repo and render the chart dry-run first so the
# platform team reviews the diff against the previous revision.
helm repo update driftlock
helm template driftlock driftlock/driftlock \
--version vX.Y.Z \
--namespace driftlock \
--values values.yaml > /tmp/driftlock-rendered.yaml
# Inspect the diff against the previous release.
diff -u <(kubectl get -o yaml -n driftlock deploy,svc,networkpolicy,hpa,secret,sa,ingress 2>/dev/null) /tmp/driftlock-rendered.yaml | less
# Install with --atomic so a failed pre-flight fires an automatic rollback.
helm install driftlock driftlock/driftlock \
--version vX.Y.Z \
--namespace driftlock \
--create-namespace \
--atomic \
--values values.yaml
# Upgrade — same flow, with --history-max bumped so the runbook can
# roll back beyond the last revision if needed.
helm upgrade driftlock driftlock/driftlock \
--version vX.Y.Z+1 \
--namespace driftlock \
--atomic \
--history-max 10 \
--values values.yaml
# Rollback — fires automatically on any failed pre-flight. Manual form:
helm rollback driftlock <REVISION> \
--namespace driftlock \
--wait \
--cleanup-on-fail
values.yaml reference
Twelve keys the platform team sets — type, default, description, and the compliance control each row maps to.
Key · type · default
Description · anchor
image.tag
type · string · default · vX.Y.Z
Pin every workload image tag. The chart bumps when the platform team sets `--atomic` on `helm upgrade`; the previous tag stays available for `helm rollback`.
anchor · FedRAMP SI-7 · SOC 2 CC6.7
image.pullPolicy
type · string · default · IfNotPresent
Defaults to `IfNotPresent` so the deploy is reproducible from the local cache. Flip to `Always` for the sandbox-runner the first time only, then back to `IfNotPresent`.
anchor · FedRAMP CM-7
replicas
type · integer · default · 1
Replica count per workload. The signing-controller + sbom-emitter always run as `1` to keep audit-trail signer_kid + Rekor uuids single-writer.
anchor · SOC 2 CC6.1
sandbox.cpu
type · integer · default · 2
CPU budget the runtime pins per concurrent sandbox step. Default 2, ceil 8 — see /docs/sandbox-config#limit-cpu.
anchor · SOC 2 CC6.1
sandbox.memory
type · string · default · 4Gi
Working-set ceiling the runtime enforces per sandbox step. Default `4Gi`, max `32Gi` — see /docs/sandbox-config#limit-memory.
anchor · SOC 2 CC6.1
sandbox.timeoutSec
type · integer · default · 1800
Hard ceiling the runtime enforces on a single sandbox step. Default 30m, max 2h — see /docs/sandbox-config#limit-timeout.
anchor · SOC 2 CC7.2
egress.allowed
type · array<string> · default · []
Allow-list of CIDR ranges the sandbox can reach — registry hosts, KMS endpoints, sigstore services, Rekor inclusion servers. Default-deny; the platform team adds ranges as integrations come online.
anchor · SOC 2 CC6.6 · FedRAMP AC-4
egress.blockedCidrs
type · array<string> · default · []
Block-list layered over `egress.allowed` — last-mile guard for known-bad registries. Keep it short and standing.
anchor · SOC 2 CC6.6 · FedRAMP AC-4
dns.policy
type · string · default · cluster
DNS posture the runtime enforces — `cluster` flows through CoreDNS, `strict` resolves only against the explicit resolver set. Reject `dns.policy: open` in code review.
anchor · SOC 2 CC6.6
tls.pinningRequired
type · boolean · default · true
TLS posture the runtime enforces on every outbound connection. Keep `true`; treat a flip to `false` as a deliberate exception the runbook records.
anchor · SOC 2 CC6.7 · FedRAMP SC-8
networkPolicy.enabled
type · boolean · default · true
Toggle the chart-managed NetworkPolicy. Default `true` — the chart pins deny-by-default + the egress allow-list on the sandbox namespace.
anchor · SOC 2 CC6.6 · FedRAMP AC-4
autoscaling.enabled
type · boolean · default · false
Horizontal Pod Autoscaler. Disabled by default — the audit-trail signer_kid + Rekor uuid are single-writer, so the signing-controller and sbom-emitter are pinned at `replicas: 1`.
anchor · SOC 2 CC6.1
templates inventory
Seven templates the chart ships — every one of them a manifest the platform team reviews at every helm upgrade.
Template · anchor
Purpose
templates/deployment.yaml
anchor · SOC 2 CC6.1 · FedRAMP CM-7
One Deployment per workload (watcher, webhook-receiver, sandbox-runner, sbom-emitter, signing-controller). Pin replicas, resources, securityContext, imagePullSecrets.
templates/service.yaml
anchor · SOC 2 CC6.6
ClusterIP Service per workload — no public exposure by default, ingress routes through the reverse-proxy chart.
templates/secret.yaml
anchor · SOC 2 CC6.1 · FedRAMP SC-12
KMS-resident secret references (cosign commit key, SBOM key, HMAC replay secret). External Secrets Operator reads from KMS and projects here.
templates/ingress.yaml
anchor · SOC 2 CC6.7 · FedRAMP SC-8
Ingress manifest for the public surface (UI + webhook-receiver). TLS termination at the reverse-proxy layer; mTLS for the internal calls the chart documents under #reverse-proxy-tls.
templates/serviceaccount.yaml
anchor · SOC 2 CC6.1 · FedRAMP AC-6
Workload ServiceAccount + IRSA / Workload Identity binding. The chart assigns least-privilege scoped to the table under `driftlock.rbac` in values.yaml.
templates/hpa.yaml
anchor · SOC 2 CC6.1
HorizontalPodAutoscaler — gated by `autoscaling.enabled`, defaults off because the audit-trail signer_kid + Rekor uuid are single-writer.
templates/networkpolicy.yaml
anchor · SOC 2 CC6.6 · FedRAMP AC-4
Default-deny + `egress.allowed` allow-list per namespace. The chart ships one for the sandbox namespace and one for the signing-controller + sbom-emitter namespace.
image.tag · sandbox.cpu · egress.allowed · tls.pinningRequired · networkPolicy.enabled · templates · helm install / upgrade / rollback
Environment variable reference
Seventeen env vars — every key cross-referenced against .env.example + the runtime schema.
Every env var the platform team sets is grounded in one of three sources of truth: .env.example, the env.ts schema, or the chart values. Secrets are hedged as KMS-injected — the platform team does not commit a real value. The DATABASE_URL row is the schema-required env var the runbook features prominently.
Name · type
R / O
Source
Kind
Default · redacted ex.
DATABASE_URL
type · url
source
env.ts schema
kind
secret · KMS
(none — KMS / chart-injected)
ex. postgres://driftlock:<redacted>@postgres.driftlock.internal:5432/driftlock?sslmode=require
NODE_ENV
type · string
source
env.ts schema
kind
config
development
ex. production
NEXT_PUBLIC_APP_URL
type · url
source
env.ts schema
kind
config
http://localhost:3000
ex. https://app.example.com
NEXT_PUBLIC_API_URL
type · url
source
env.ts schema
kind
config
(unset → same-origin /api)
ex. https://api.example.com
POLSIA_IMAGE_REMOTE_HOSTS
type · string
source
.env.example
kind
config
(unset → no remote hosts)
ex. cdn.example.com,assets.example.com
SEO_INDEXABLE
type · boolean
source
.env.example
kind
config
(unset → robots Disallow: /)
ex. true
BETTER_AUTH_SECRET
type · string
source
env.ts schema
kind
secret · KMS
(none — KMS / chart-injected)
ex. <32-byte random hex>
BETTER_AUTH_URL
type · url
source
env.ts schema
kind
config
(none — chart-injected, mirrors NEXT_PUBLIC_APP_URL)
ex. https://app.example.com
BETTER_AUTH_TRUSTED_ORIGINS
type · string
source
env.ts schema
kind
config
(empty)
ex. https://app.example.com,https://admin.example.com
POLSIA_OWNER_EMAIL
type · string
source
env.ts schema
kind
config
(empty — set at first install)
ex. <owner-email-redacted>
POLSIA_EMAIL_PROXY_URL
type · url
source
env.ts schema
kind
secret · KMS
(none — platform-injected)
ex. https://<email-proxy-hostname>/v1
DRIFTLOCK_KMS_KEY_ALIAS
type · string
source
chart values
kind
config
driftlock/commit-signer
ex. driftlock/commit-signer
DRIFTLOCK_WEBHOOK_SHARED_SECRET_ALIAS
type · string
source
chart values
kind
secret · KMS
driftlock/webhook-hmac
ex. driftlock/webhook-hmac
DRIFTLOCK_REKOR_URL
type · url
source
chart values
kind
config
https://rekor.driftlock.dev
ex. https://rekor.driftlock.dev
DRIFTLOCK_OIDC_ISSUER
type · url
source
chart values
kind
config
(none — chart-injected, mirrors the OIDC IdP)
ex. https://oidc.example.com
DRIFTLOCK_EGRESS_ALLOWED
type · string
source
chart values
kind
config
[]
ex. registry.example.com/32,rekor.example.com/32,oidc.example.com/32
DRIFTLOCK_HERMETIC
type · string
source
chart values
kind
config
0
ex. 1
DATABASE_URL · NEXT_PUBLIC_APP_URL · BETTER_AUTH_SECRET · DRIFTLOCK_KMS_KEY_ALIAS · chart values · KMS-injected
Reverse proxy & TLS
Three proxy variants — HSTS, OCSP stapling, mTLS, ALPN, source-IP allow-list.
The reverse-proxy graph splits in two layers. The edge layer (nginx / Caddy) terminates TLS for the public surface — HSTS preload-ready, OCSP stapling on, ACME enrollment through the chart's driftlock.acme.issuer config. The internal layer (Envoy on mTLS) terminates the workload-mTLS cert for the cluster-internal calls between sandbox-runner ↔ signing-controller ↔ sbom-emitter.
Reverse-proxy + TLS
nginx
Public ingress for the UI + webhook-receiver — TLS termination at the edge, HSTS (preload-ready), OCSP stapling, and `X-Forwarded-*` plumbing for the in-cluster workloads.
# nginx.conf — illustrated. Pin the TLS protocol set, enable OCSP
# stapling, and forward the source IP for the audit-trail row.
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_protocols TLSv1.3;
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "DENY" always;
# ACME enrollment handled by the upstream cert-manager + the
# staging / prod issuance flow the chart annotates under
# driftlock.acme.issuer.
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
location / {
proxy_pass http://driftlock-webhook-receiver.driftlock.svc.cluster.local:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}
anchor · SOC 2 CC6.7 · FedRAMP SC-8 · FedRAMP SC-13
Reverse-proxy + TLS
Caddy
Same edge role as nginx — automatic ACME enrollment via the Caddyfile, default HSTS, default OCSP stapling, on-demand TLS for the source-IP allow-list hostname.
# Caddyfile — illustrated. Caddy auto-issues + renews the cert from
# the ACME issuer named in the chart, default HSTS + OCSP stapling
# are on. Set the source-IP allow-list under 'evaluate_order' so
# the cert is only minted from a known egress.
app.example.com, webhook.example.com {
encode zstd gzip
reverse_proxy http://driftlock-webhook-receiver.driftlock.svc.cluster.local:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
}
tls {
issuer acme {
email ops@example.com
directory https://acme-v02.api.letsencrypt.org/directory
on_demand
}
alpn h2 http/1.1
protocols tls1.3
}
log {
output file /var/log/caddy/driftlock.log {
roll_size 10mb
roll_keep 5
}
}
}
anchor · SOC 2 CC6.7 · FedRAMP SC-8
Reverse-proxy + TLS
Envoy
Internal mTLS for the sandbox ↔ signing-controller + sbom-emitter calls. Envoy fronts the cluster-internal listener, terminates the workload-mTLS cert from the cluster issuer, and applies the source-IP allow-list the audit-trail row records.
# envoy.yaml — illustrated. Terminate mTLS on the cluster-internal
# listener, validate the workload cert against the in-cluster
# issuer, and apply the source-IP allow-list the chart ships
# under driftlock.egress.allowed.
static_resources:
listeners:
- name: driftlock-internal_mtls
address: { socket_address: { address: 0.0.0.0, port_value: 8443 } }
filter_chains:
- transport_socket:
name: envoy.transport_sockets.tls
typed_config:
common_tls_context:
tls_certificate_sds_secret_configs:
- name: workload_cert
sds_config:
path: /etc/envoy/sds/workload.yaml
validation_context_sds_secret_config:
name: workload_validation
sds_config: { path: /etc/envoy/sds/validation.yaml }
alpn_protocols: [h2, http/1.1]
filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
stat_prefix: driftlock_internal
route_config:
virtual_hosts:
- name: driftlock
domains: ['*']
routes:
- match: { prefix: '/' }
route: { cluster: signing_controller_cluster }
http_filters:
- name: envoy.filters.http.router
# Source-IP allow-list: drop anything not in driftlock.egress.allowed.
# Envoy reads this from a ConfigMap the chart re-renders at every
# helm upgrade.
clusters:
- name: signing_controller_cluster
connect_timeout: 1s
load_assignment:
cluster_name: signing_controller_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: driftlock-signing-controller.driftlock.svc.cluster.local
port_value: 8080
anchor · SOC 2 CC6.7 · FedRAMP SC-8 · FedRAMP IA-2
nginx · Caddy · Envoy · HSTS · OCSP stapling · mTLS · ALPN · X-Forwarded-* · source-IP allow-list · ACME enrollment
Upgrade runbook
Seven steps — pre-flight, backup verify, apply (rolling or blue/green), smoke, rollback.
One page the platform team prints and pins. Each step names the phase, the action, the verification, and the rollback tactic. The phases are colour-coded so the on-call engineer reads the runbook status at a glance — pre-flight + verify start the runbook, the amber apply phase charts the diff, the emerald smoke phase confirms success, and the rose rollback tactic fires automatically on a failed pre-flight.
upgrade runbook · up-pre-flight
- Action
- Run `helm template driftlock driftlock/driftlock --version vX.Y.Z+1 -f values.yaml` and diff against the previous rendered release. The chart pre-flight must show every workload green before the platform team moves to backup verify.
- Verification
- Diff returns zero unowned changes — only `image.tag` differs and any `sandbox.*` / `egress.*` keys the platform team intended to bump. A spurious change to `networkPolicy.enabled`, `tls.pinningRequired`, or `dns.policy` blocks the upgrade.
- Rollback
- Stop before `helm upgrade` — the runbook has not started, no service disruption.
upgrade runbook · up-backup-verify
- Action
- Trigger `pg_basebackup` + WAL-archive integrity check (`pg_verifybackup`) and confirm PITR replay covers at least the planned upgrade window + 24h of headroom.
- Verification
- `pg_verifybackup` exits 0; the PITR test target is restored to a scratch namespace and round-trips a known `signed-commit` audit-trail row at the cell the row occupies.
- Rollback
- Investigate before continuing — a failing backup freezes the upgrade until the runbook records the failing window.
upgrade runbook · up-rolling-vs-blu-green
- Action
- Default path: rolling (`helm upgrade --atomic`, one workload at a time, pre-flight green per workload before the next). Choose blue/green when (a) the diff touches `sandbox.timeoutSec` upward, (b) a major version change to `signing-controller`, or (c) the diff mounts a new KMS alias. Blue/green ships the new chart to `driftlock-canary` first; smoke tests run there, then the live namespace atomically swaps recorders.
- Verification
- Rolling: every workload reports `Ready` within the chart timeout (default 5m) before the next one lands. Blue/green: `driftlock-canary` round-trips a smoke PR before live flips.
- Rollback
- Rolling: `helm rollback driftlock <REVISION>` revives the previous revision; signing-controller + sbom-emitter resume single-writer in seconds. Blue/green: live keeps running on the previous revision while the canary is debugged.
upgrade runbook · up-image-tag-strategy
- Action
- Pin `image.tag` to a specific semver + a `+build` suffix the chart honors (e.g. `vX.Y.Z+1`). Never auto-bump — every upgrade lands a new signer_kid + Rekor uuid, so the diff is auditable.
- Verification
- `helm get values driftlock -n driftlock | grep image.tag` returns the semver-locked value; `cosign verify` against the registry confirms the pull-side signature is valid.
- Rollback
- Pre-rollback `image.tag` is the value the platform team re-points in the chart repo — the previous revision is the source of truth.
upgrade runbook · up-db-compat
- Action
- Cross-check the new chart against the DB compatibility matrix the platform team maintains: PostgreSQL version (16+), Prisma migration list (in `driftlock.db.migrations`), and any `ALTER TABLE` the chart auto-runs at pre-flight.
- Verification
- `helm template` post-render includes the migration `Job` and the `pre-upgrade` hook lists only DB-roll-forward migrations. The `pre-downgrade` hook (if any) lists the matching rollback migrations — a missing rollback freezes the upgrade.
- Rollback
- Stop before the pre-upgrade hook fires; restore from the backup the runbook verified seconds earlier.
upgrade runbook · up-post-smoke
- Action
- After the chart lands, run the post-upgrade smoke set the chart ships under `tools/smoke`: (1) open a draft PR through the watcher, (2) verify the audit-trail row lands with the new `image.tag`, (3) verify a SBOM CycloneDX document emits + the Rekor uuid is recorded, (4) verify a cosign signature lands with the new `signer_kid`.
- Verification
- All four smoke checks exit 0 within 5m. The audit-trail row carries the new image tag + signer kid; the SBOM hash + Rekor uuid match the values rendered in `helm template`.
- Rollback
- If any smoke check fails, `helm rollback driftlock <REVISION>` fires; the previous revision resumes and the smoke target is debugged in a scratch namespace.
upgrade runbook · up-post-record
- Action
- After the smoke set passes, record the upgrade in the change-management system the platform team owns — chart version, signer_kid delta, Rekor uuid delta, and the smoke check report are all citations the compliance reviewer reads.
- Verification
- The change ticket carries the chart version, the audit-trail row id, the SBOM hash, and the Rekor uuid; the row is searchable by `chart_version` for future audits.
- Rollback
- A retroactive record does not unwind an upgrade — the runbook instead flags the row for a follow-up audit cycle if a discrepancy surfaces.
pre-flight · verify · apply · smoke · rollback · audit-linked · reversible
DB migration
Four hooks — pre-upgrade verify + post-upgrade idempotency + a rollback tactic.
The Helm chart ships an install Job that runs prisma migrate deploy against driftlock.db.migrations. The migration set is verified against a pg_verifybackup snapshot the runbook takes seconds ahead of the chart bump; a failed migration fails closed and triggers the rollback hook before any runtime restart.
Idempotent Prisma migrations applied by the Helm-managed install Job
Every chart-bump triggers the Job in this order: pre-upgrade (Provision the cluster-side prerequisites the chart expects) → verify (run pg_verifybackup + a PITR warm-up) → post-upgrade (re-run migrations to confirm idempotency + row-shape sanity) → rollback (only on a failed hook fire). The chart bumps are byte-stable across reruns — the post-upgrade migrations table the platform team consults is the same one the PR-immutable audit log cross-references against every signer_kid the signing-controller stamps.
# Install Job ordering — the chart composes the four hooks
# into a sequence the install Job runs in order. Each hook is a Helm
# Job that the chart creates with retryStrategy: { limit: 3 }.
# pre-upgrade — runs before any Deployment is updated:
helm install driftlock driftlock/driftlock --atomic --values values.yaml
# → pre-upgrade Job: kubectl get jobs -n driftlock -l hook=pre-upgrade
# post-upgrade — runs after the chart bumps:
# → post-upgrade Job: kubectl get jobs -n driftlock -l hook=post-upgrade
# → on a non-zero exit the chart refuses to mark the install READY and
# surfaces a Helm rollbackable event into #rbac-matrix db migration · mig-pre-upgrade-hook
- Command
- Run `helm install driftlock driftlock/driftlock --atomic --values values.yaml` against the chart; the chart-managed install Job runs `prisma migrate deploy` idempotently against `driftlock.db.migrations`. The Job waits for the migration set to report `applied` for every entry the chart contains before the chart marks `READY`.
- Verification
- Run `kubectl logs -n driftlock deploy/driftlock-install --since=2m` and confirm the log tail shows `6 migration(s) applied, 0 pending, 0 failed` (the exact count the chart pins on the version the platform team installs). `prisma migrate status` over a Node Job in the same namespace reports `Database schema is up to date`.
- Rollback
- If a migration reports `failed`, `helm rollback driftlock <REVISION>` re-points the chart at the previous image; the pre-upgrade hook halts before the runtime restarts. Roll the schema back with `prisma migrate resolve --rolled-back <name>` and audit the failed migration under #rbac-matrix `rbac.set` grant of the security reviewer.
db migration · mig-backup-verify
- Command
- Before the chart-managed install Job runs, the runbook spins a `pg_basebackup` against the WAL archive and runs `pg_verifybackup` over the manifest. PITR replay covers at least the planned migration window + 24h of headroom so a migration that lands against a snapshot can still be replayed transactionally.
- Verification
- `pg_verifybackup` exits 0; the PITR test target is restored to a scratch namespace and round-trips a known audit-trail row + SBOM sha256 at the cell the row occupies. The scratch namespace is destroyed at the end of verify so the cluster has no residual state from the drill.
- Rollback
- Investigate before the install Job starts; a failing backup freezes the migration window until the runbook records the failing restore + the audit-trail row id the failure pinned.
db migration · mig-post-upgrade
- Command
- After the chart lands, the post-upgrade hook runs `prisma migrate deploy` one more time to confirm idempotency, then a sanity query that asserts every audit-trail row written since the chart bump carries the new `image.tag` + `signer_kid` cells. The Job exits 0 on a clean idempotency check and non-zero on any drift.
- Verification
- Tail the Job log and confirm `idempotent re-run ok` plus the row-shape sanity query returning the post-upgrade tag set across the last 50 PRs. A non-zero exit blocks the post-upgrade phase from transitioning to `smoke`.
- Rollback
- On a non-zero exit, the chart applies the matching `prisma migrate resolve --rolled-back` for the entry that drifted, and the runbook routes the failed row through the `rbac.set` grant under #rbac-matrix to the security reviewer for sign-off.
db migration · mig-rollback
- Command
- If the pre-upgrade hook fires a non-zero exit, the chart marks the install Job as failed, refuses to advance the post-upgrade hook, and the runbook restores the schema from the `pg_basebackup` snapshot the verify hook took seconds earlier via `pg_restore` against the live cluster.
- Verification
- After the restore, `prisma migrate status` returns `Database schema is up to date` against the prior revision and the audit-trail row count matches the snapshot — no missing rows. The runbook records the failed revision + the row id of the migration that was rolled back.
- Rollback
- A second rollback attempt is a P1 incident: page the on-call rotation through the watcher input the platform team already owns (PagerDuty / Opsgenie), and route the schema through the security reviewer for an explicit `rbac.set` sign-off before any retry.
pre-upgrade · verify · post-upgrade · rollback · Prisma · pg_verifybackup · PITR · idempotent · audit-linked
Overnight cron
Three schedules — 02:14 UTC scan · 03:00 UTC sbom-reaper · 04:00 UTC Sun rekor-prune.
Three cron-cadenced jobs the platform team registers in their chart repo. The cadence uses the same 24-hour anchor + recurrence a Datadog monitor or a PagerDuty escalation reads; UTC is the only timezone the schedule accepts so a platform team in Tokyo and a security reviewer in Berlin see the same driftlock cron list output.
overnight cron · cron-overnight-scan
02:14 UTC · daily
set: cron driftlock.overnight-scan \
command "kubectl exec -n driftlock deploy/watcher -- /usr/local/bin/driftlock-scan --since 06h" \
at 02:14 UTC dailyRun `driftlock cron list | grep driftlock.overnight-scan` and confirm schedule `2 14 * * *` with the watcher Job image; the next-run timestamp matches 02:14 UTC ± 1m on the same date. A canonical run lands a `branch_draft` row in the audit trail the platform team can replay from the alert id.
anchor · /docs/quickstart#ingest
overnight cron · cron-sbom-reaper
03:00 UTC · daily
set: cron driftlock.sbom-reaper \
command "kubectl exec -n driftlock deploy/sbom-emitter -- /usr/local/bin/driftlock-sbom-reaper --older-than 24h" \
at 03:00 UTC dailyRun `driftlock cron list | grep driftlock.sbom-reaper` and confirm schedule `0 3 * * *` with the sbom-emitter image; the next-run timestamp matches 03:00 UTC. The reaper Job prunes stale SBOM delta entries that no merged PR references and rotates the SBOM-signing alias on a 90-day cadence.
anchor · /docs/compliance#sbom-emission
overnight cron · cron-rekor-prune
04:00 UTC · weekly · Sunday
set: cron driftlock.rekor-prune \
command "kubectl exec -n driftlock deploy/sbom-emitter -- /usr/local/bin/driftlock-rekor-prune --older-than 30d --dry-run" \
at 04:00 UTC on SundayRun `driftlock cron list | grep driftlock.rekor-prune` and confirm schedule `0 4 * * 0` with the sbom-emitter image; the next-run timestamp matches 04:00 UTC on Sunday. The dry-run emits a report the platform team reviews; the prune is only enabled once the report sign-off lands under the `rbac.set` grant.
anchor · /docs/compliance#merge-audit-trail
overnight-scan · sbom-reaper · rekor-prune · UTC anchor · audit-linked
RBAC bootstrap
Six roles × eleven actions — the literal .driftlock/config.yaml the platform team commits at install.
The RBAC matrix on /docs/compliance#rbac-matrix renders the six roles × eleven actions grant table a reviewer reads first. This page ships the literal .driftlock/config.yaml snippet the platform team commits at install, plus a six-row acknowledgement list that maps the snippet back to the compliance matrix verbatim.
.driftlock/config.yaml — RBAC bootstrap snippet
The snippet below is illustrative. The platform team commits the actual file to their chart repo and reviews the diff at every chart bump; every rbac.set requires_sign_off cell is a dual sign-off gate the runtime enforces — no delegated override.
# .driftlock/config.yaml — RBAC bootstrap the platform team commits at
# install. Mirrors the six roles × eleven actions grant table on
# /docs/compliance#rbac-matrix verbatim. Cells that need a sign-off carry
# "requires_sign_off: true"; unlisted (role, action) pairs default to deny.
#
# This file is the seam a reviewer reads first. Bootstrap with:
# kubectl create configmap driftlock-rbac \
# --from-file=config.yaml=.driftlock/config.yaml -n driftlock
# then restart the signing-controller + sbom-emitter so the new grants land.
rbac:
matrix_version: '1'
default_grant: deny
roles:
- id: role-admin
identity: human
grants:
chart.install: { grant: allow, requires_sign_off: false }
chart.upgrade: { grant: allow, requires_sign_off: false }
kms.rotate: { grant: allow, requires_sign_off: false }
rbac.set: { grant: allow, requires_sign_off: false }
egress.set: { grant: allow, requires_sign_off: false }
retention.set: { grant: allow, requires_sign_off: false }
sign.commit: { grant: allow, requires_sign_off: false }
approver.signoff: { grant: allow, requires_sign_off: false }
audit.read: { grant: allow, requires_sign_off: false }
sbom.read: { grant: allow, requires_sign_off: false }
sbom.emit: { grant: allow, requires_sign_off: false }
- id: role-security-reviewer
identity: human
grants:
chart.install: { grant: audit-only }
chart.upgrade: { grant: audit-only }
kms.rotate: { grant: audit-only }
rbac.set: { grant: audit-only }
egress.set: { grant: audit-only }
retention.set: { grant: audit-only }
sign.commit: { grant: deny }
approver.signoff: { grant: allow, requires_sign_off: false }
audit.read: { grant: allow, requires_sign_off: false }
sbom.read: { grant: allow, requires_sign_off: false }
sbom.emit: { grant: deny }
- id: role-operator
identity: human
grants:
chart.install: { grant: allow, requires_sign_off: true }
chart.upgrade: { grant: allow, requires_sign_off: true }
kms.rotate: { grant: deny }
rbac.set: { grant: deny }
egress.set: { grant: allow, requires_sign_off: true }
retention.set: { grant: allow, requires_sign_off: true }
sign.commit: { grant: deny }
approver.signoff: { grant: allow, requires_sign_off: false }
audit.read: { grant: allow, requires_sign_off: false }
sbom.read: { grant: allow, requires_sign_off: false }
sbom.emit: { grant: audit-only }
- id: role-watcher-bot
identity: automation
grants:
chart.install: { grant: deny }
chart.upgrade: { grant: deny }
kms.rotate: { grant: deny }
rbac.set: { grant: deny }
egress.set: { grant: deny }
retention.set: { grant: deny }
sign.commit: { grant: deny }
approver.signoff: { grant: deny }
audit.read: { grant: allow, requires_sign_off: false }
sbom.read: { grant: allow, requires_sign_off: false }
sbom.emit: { grant: deny }
- id: role-sandbox-runner
identity: workload
grants:
chart.install: { grant: deny }
chart.upgrade: { grant: deny }
kms.rotate: { grant: deny }
rbac.set: { grant: deny }
egress.set: { grant: deny }
retention.set: { grant: deny }
sign.commit: { grant: deny }
approver.signoff: { grant: deny }
audit.read: { grant: allow, requires_sign_off: false }
sbom.read: { grant: allow, requires_sign_off: false }
sbom.emit: { grant: allow, requires_sign_off: false }
- id: role-signing-controller
identity: workload
grants:
chart.install: { grant: deny }
chart.upgrade: { grant: deny }
kms.rotate: { grant: deny }
rbac.set: { grant: deny }
egress.set: { grant: deny }
retention.set: { grant: deny }
sign.commit: { grant: allow, requires_sign_off: false }
approver.signoff: { grant: deny }
audit.read: { grant: allow, requires_sign_off: false }
sbom.read: { grant: allow, requires_sign_off: false }
sbom.emit: { grant: deny }
Per-role acknowledgement
Six rows — one per role. Each row names the scope-of-grant, the gated actions, and the hard-DENY notes the runbook records. Every row is anchored against /docs/compliance#rbac-matrix so a reviewer pivots between the bootstrap snippet and the compliance row table without translating any term.
Role · scope
Gated actions
Hard DENY
role-admin
Owns the platform: installs the chart, rotates KMS keys, edits the RBAC matrix, signs commits, and reads every audit + SBOM row.
chart.install · chart.upgrade · kms.rotate · rbac.set · egress.set · retention.set · sign.commit · approver.signoff · audit.read · sbom.read · sbom.emit
Nothing — the admin role carries a full grant over the eleven actions the compliance matrix enumerates.
role-security-reviewer
Read-only access to the audit trail + SBOM registry, sign-off on approver-chain edits, queries the Rekor log. Cannot mutate chart values or rotate keys.
approver.signoff · audit.read · sbom.read · (audit-only on chart.install / chart.upgrade / kms.rotate / rbac.set / egress.set / retention.set)
sign.commit · sbom.emit — security reviewers never stamp a commit or write a SBOM themselves; the signer_kid + Rekor uuid stay single-writer through the signing-controller.
role-operator
Day-to-day helm upgrades + sandbox knob diffs + retention-tier changes. Every action lands an approver-chain row signed by the actor.
chart.install (sign-off) · chart.upgrade (sign-off) · egress.set (sign-off) · retention.set (sign-off) · approver.signoff · audit.read · sbom.read · sbom.emit (audit-only)
kms.rotate · rbac.set · sign.commit — operators never rotate the KMS-resident keys or edit the RBAC matrix; a failed mutation fails closed and pages the security reviewer.
role-watcher-bot
Ingests inbound webhooks from the GitHub App + CI + dependency feeds; correlates signals against alerts and advisories.
audit.read · sbom.read
Every mutation action — chart.install · chart.upgrade · kms.rotate · rbac.set · egress.set · retention.set · sign.commit · approver.signoff · sbom.emit. The watcher never modifies cluster state.
role-sandbox-runner
Executes hermetic runs under the assigned toolchain; emits SBOMs + Rekor claims for every successful run.
audit.read · sbom.read · sbom.emit
Every cluster-level mutation — chart.install · chart.upgrade · kms.rotate · rbac.set · egress.set · retention.set · sign.commit · approver.signoff. The sandbox cannot mutate the chart that gave it life.
role-signing-controller
Reads the cosign commitment, fetches an OIDC token, stamps the Driftlock-attributed commit. Single-writer; signer_kid + Rekor uuid chain to the prior emission.
sign.commit · audit.read · sbom.read
kms.rotate · rbac.set · sbom.emit · every approver / harness action. The signing-controller never rotates its own key or self-promotes in the matrix.
admin · security reviewer · operator · watcher-bot · sandbox-runner · signing-controller · six × eleven · grant table · hard DENY
Back to the runbook
The parent /docs page covers the overview — this page is the deep-dive a platform engineer reads end to end.
The three-subsection selfHosting overview on /docs → Self-hosting names the prerequisites + install + upgrade bullets. This deep-dive expands each bullet — seven prerequisites with verification rows, seven compose services + the starter snippet, twelve Helm values + seven templates + install / upgrade commands, seventeen env vars cross-referenced against .env.example and the runtime schema, three reverse-proxy + TLS snippets (nginx · Caddy · Envoy), and the seven-step one-page upgrade runbook a security reviewer reads end to end.
Request a deeper-docs walkthrough
We'll reply with a calendar link and the air-gap or key-handling runbook relevant to your scope — and a copy of the values.yaml starter we ship behind SSO once your team's SSO is on file.