Key points
- Set
NOPSAI_REQUIRE_PRODUCTION_GATES=trueso an unsafe configuration fails startup instead of running. - Set
DISPATCHER_TLS_MODEtomtlsortls. Neverdisabled. - Generate distinct high-entropy values for
JWT_SIGNING_KEYandSERVICE_JWT_SIGNING_KEY. - Replace every Compose fallback secret, and leave
NOPSAI_BOOTSTRAP_ADMIN_ALLOW_DEFAULT_PASSWORDfalse. - Keep
NOPSAI_BOOTSTRAP_ADMIN_MUST_CHANGE_PASSWORDtrue so the provisioned password rotates at first login. - Point
SYSTEM_LOGS_DOCKER_HOSTat the restricted socket proxy, never the raw Docker socket. - Set
METRICS_REQUIRE_AUTH=truewhen/metricsis reachable outside the cluster network. - Mount
DATA_BACKUP_DIRas durable storage — the default Compose topology does not.
Before you start
- Environment
- A non-Compose deployment: a release bundle or the Helm chart
- Secret store
- A place to hold the platform secrets that is not a local
.env - Administrator access
- Enough access to change system settings and identity configuration
Steps
- 01
Replace every bootstrap value
The values that make a local install convenient are the ones that make a shared install dangerous. Rotate them all before anyone else connects.
ImportantJWT_SIGNING_KEYandSERVICE_JWT_SIGNING_KEYmust differ, or a user token can be replayed as a service token.
Values that must be install-specificbash NOPSAI_MASTER_KEY JWT_SIGNING_KEY SERVICE_JWT_SIGNING_KEY AAA_SHARED_INTERNAL_TOKEN POSTGRES_PASSWORD NOPSAI_BOOTSTRAP_ADMIN_PASSWORD - 02
Close the network surface
Only the Git webhook ingress needs to be reachable from outside. The API, dispatcher, and database do not.
Verify- From outside the cluster or host, the dispatcher gRPC port and PostgreSQL port do not answer.
- 03
Move identity off local accounts
Configure an identity provider and reduce local accounts to break-glass. Provider ID, issuer, and subject are the identity; email is metadata.
Verify- Signing in through the provider produces a session with the expected roles.
- 04
Confirm the startup gates pass
Production startup gates refuse to start a service that is still carrying development defaults. A clean start is the check.
Check platform identity and healthbash curl -s "$NOPSAI_URL/version" | jq curl -s "$NOPSAI_URL/healthz"Verify- Every service starts,
/healthzis ready, and no gate error appears in the system logs.
- Every service starts,
How it works
Rotate SERVICE_JWT_SIGNING_KEY and DISPATCHER_TLS_SECRET together before exposing a replacement dispatcher. Keeping the old values lets retired runner definitions authenticate again unless their IDs are carried in ejected_runner_ids.
Keep step pods on the no-RBAC workload service account. Merging it with the runner service account gives workload code access to the Kubernetes API.
Review egress: Docker step containers default to bridge networking. Use DOCKER_NETWORK_NAME=none or a dedicated network when workloads should not reach the internet.
Prefer credential references over the legacy inline secret variables for GitHub App keys, webhook secrets, LLM keys, and MCP tokens.
Implementation evidence
doc/enterprise-gates.mdProduction startup gates and verification commands.
pkg/startupgates/startupgates.goWhat the gates actually check.

