Docs · reference

self-host deployment
security-reviewer ready

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

R

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

R

source

env.ts schema

kind

config

development

ex. production

NEXT_PUBLIC_APP_URL

type · url

R

source

env.ts schema

kind

config

http://localhost:3000

ex. https://app.example.com

NEXT_PUBLIC_API_URL

type · url

O

source

env.ts schema

kind

config

(unset → same-origin /api)

ex. https://api.example.com

POLSIA_IMAGE_REMOTE_HOSTS

type · string

O

source

.env.example

kind

config

(unset → no remote hosts)

ex. cdn.example.com,assets.example.com

SEO_INDEXABLE

type · boolean

O

source

.env.example

kind

config

(unset → robots Disallow: /)

ex. true

BETTER_AUTH_SECRET

type · string

R

source

env.ts schema

kind

secret · KMS

(none — KMS / chart-injected)

ex. <32-byte random hex>

BETTER_AUTH_URL

type · url

R

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

O

source

env.ts schema

kind

config

(empty)

ex. https://app.example.com,https://admin.example.com

POLSIA_OWNER_EMAIL

type · string

O

source

env.ts schema

kind

config

(empty — set at first install)

ex. <owner-email-redacted>

POLSIA_EMAIL_PROXY_URL

type · url

R

source

env.ts schema

kind

secret · KMS

(none — platform-injected)

ex. https://<email-proxy-hostname>/v1

DRIFTLOCK_KMS_KEY_ALIAS

type · string

R

source

chart values

kind

config

driftlock/commit-signer

ex. driftlock/commit-signer

DRIFTLOCK_WEBHOOK_SHARED_SECRET_ALIAS

type · string

R

source

chart values

kind

secret · KMS

driftlock/webhook-hmac

ex. driftlock/webhook-hmac

DRIFTLOCK_REKOR_URL

type · url

O

source

chart values

kind

config

https://rekor.driftlock.dev

ex. https://rekor.driftlock.dev

DRIFTLOCK_OIDC_ISSUER

type · url

R

source

chart values

kind

config

(none — chart-injected, mirrors the OIDC IdP)

ex. https://oidc.example.com

DRIFTLOCK_EGRESS_ALLOWED

type · string

R

source

chart values

kind

config

[]

ex. registry.example.com/32,rekor.example.com/32,oidc.example.com/32

DRIFTLOCK_HERMETIC

type · string

O

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

proxy-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

proxy-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

proxy-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 daily

Run `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 daily

Run `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 Sunday

Run `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.