Deploy and operate
Documentation

Version Latest

Docker Compose

Run Neural Inverse Govern on a single host with Docker Compose, from first start to a production configuration.

The Docker Compose stack runs Govern (Keystone) on one host. Run every command from the root of the Keystone release; the root docker-compose.yml includes deploy/docker-compose.yml.

Requirements

  • Docker 24.0 or later
  • Docker Compose v2.20 or later (docker compose version)
  • 2 GB of available memory and 10 GB of disk

Services

ServicePortPurpose
postgres5432PostgreSQL 17
db-rolesCreates the three database roles on every start, then exits
migrateApplies the migrations as keystone_owner, then exits
server8080The Govern server (ni-auth)
dashboard3000Web dashboard and sign-in UI
mailpit1025 (SMTP), 8025 (web)Captures email for local development

The server connects as keystone_app under row-level security and refuses to start as a superuser or a role that can bypass row-level security. The backend network is internal; only the published ports are reachable from the host.

Configure

Copy the example environment file:

cp .env.example .env

The stack refuses to start until these variables have values. Generate each one separately:

VariableGenerate
KEYSTONE_OWNER_PASSWORD, KEYSTONE_APP_PASSWORD, KEYSTONE_PLATFORM_PASSWORDopenssl rand -hex 24 (three different values)
KMS_MASTER_KEYopenssl rand -hex 32
NI_AUTH_API_KEY_PEPPER, NI_AUTH_OAUTH2_PEPPER, NI_AUTH_EMAIL_TOKEN_PEPPERopenssl rand -base64 48
NI_AUTH_AUDIT_HMAC_KEY, NI_AUTH_AUDIT_SIGNING_KEYopenssl rand -base64 48
NI_OBSERVABILITY_SIGNING_KEYopenssl rand -base64 48
NI_AUTH_VAULT_ENCRYPTION_KEY, NI_AUTH_SECRETS_ENCRYPTION_KEYopenssl rand -base64 48
NI_AUTH_PASSPORT_KEY, NI_AUTH_APPROVAL_SECRETopenssl rand -base64 48
NI_AUTH_TOTP_ENCRYPTION_KEY, NI_AUTH_LDAP_ENCRYPTION_KEY, NI_AUTH_SOCIAL_ENCRYPTION_KEY, NI_AUTH_WEBHOOK_ENCRYPTION_KEYopenssl rand -base64 32

.env.example contains placeholder values for some of these. With NI_AUTH_DEPLOY_ENV unset (production), the server refuses placeholders, all-zero keys and anything containing change-me or replace-with. For local development only, NI_AUTH_DEPLOY_ENV=development accepts them with a warning at every start.

The OIDC signing key is mounted from deploy/dev/oidc-private.pem by default, a development key that ships with the repository. For any other deployment, generate a key and point NI_AUTH_OIDC_PRIVATE_KEY_HOST_FILE at it:

openssl genrsa -out oidc-private.pem 4096

Start

docker compose up --build -d
docker compose ps
curl http://localhost:8080/healthz

The server answers at http://localhost:8080, the dashboard at http://localhost:3000 and Mailpit at http://localhost:8025.

Run in production

Put TLS in front of the server

The server does not terminate TLS. A minimal Caddy configuration:

auth.example.com {
  reverse_proxy server:8080
}

app.example.com {
  reverse_proxy dashboard:3000
}

List the proxy's addresses in NI_AUTH_TRUSTED_PROXIES (CIDRs or IPs). Forwarding headers are believed only from the proxies you list, so a client cannot choose the address used for rate limits, lockouts, risk scoring and audit events. If a CDN sits in front of the proxies, also set NI_AUTH_TRUSTED_CDN_PROXIES and NI_AUTH_CLIENT_IP_HEADER.

Set the public URLs and email

NI_AUTH_PUBLIC_URL=https://auth.example.com
NI_AUTH_JWT_ISSUER=https://auth.example.com
NI_AUTH_SMTP_HOST=smtp.example.com
NI_AUTH_SMTP_PORT=587
NI_AUTH_SMTP_STARTTLS=true
NI_AUTH_SMTP_USERNAME=postmaster@example.com
NI_AUTH_SMTP_PASSWORD=<smtp-password>
NI_AUTH_SMTP_FROM=no-reply@example.com
POSTGRES_PASSWORD=<strong-random-password>
  • NI_AUTH_PUBLIC_URL must be HTTPS in production, and NI_AUTH_JWT_ISSUER must match it exactly.
  • If the API is served on its own host, set NI_AUTH_API_PUBLIC_URL (for example https://api.example.com). Model and MCP gateway addresses and other client-facing URLs then use it, while sign-in paths keep NI_AUTH_PUBLIC_URL.
  • NI_AUTH_REGISTRATION_MODE is invite by default (an invitation is required); open lets anyone create an account.
  • Never set NI_AUTH_SMTP_INSECURE_SKIP_VERIFY=true in production.

Keep the database private

Change POSTGRES_PASSWORD, enable TLS to PostgreSQL, and do not publish port 5432 beyond the host. The internal worker API (port 9090) is published on loopback only; never bind it to a public address.

Store files

Exports and other files are written to the keystone_data volume (NI_OBJECT_STORE_DIR). To use an S3 or S3-compatible bucket instead, set NI_OBJECT_STORE_S3_BUCKET with its region, KMS key, prefix and endpoint variables.

Turn on optional features

FeatureVariables
Model Access manifest signingNI_AUTH_MODELS_SIGNING_KEY_PEM, NI_AUTH_MODELS_SIGNING_KEY_ID (Ed25519 PKCS#8); without them /api/v1/models/resolved answers 503
Agent policy manifest signingNI_AUTH_AGENTPOLICY_SIGNING_KEY_PEM, NI_AUTH_AGENTPOLICY_SIGNING_KEY_ID; without them the resolved policy answers 503
MCP manifest signingKEYSTONE_MCP_SIGNING_KEY_PEM (or _FILE), KEYSTONE_MCP_SIGNING_KEY_ID
DLP tokenizationNI_AUTH_DLP_TOKEN_ENCRYPTION_KEY (openssl rand -base64 32); without it tokenization answers 503 and stores nothing
Audit tieringNI_AUTH_AUDIT_TIERING_*; see Retention and tiering
Prometheus scrapesNI_AUTH_METRICS_TOKEN

Generate an Ed25519 signing key with openssl genpkey -algorithm ed25519. Keep each key ID stable until you rotate.

Migrations

The migrate service applies migrations on every docker compose up, as the schema owner keystone_owner, and exits with code 0 when they are all applied. To run them by hand:

docker compose run --rm migrate

See Upgrades and migrations for the database roles involved and what to do when a migration fails.

Backup and restore

PostgreSQL data is in the postgres_data volume, and exports and artifacts in the keystone_data volume. Back up the database in the custom format with the bootstrap superuser:

docker compose exec -T postgres pg_dump -U ni_auth -d ni_auth \
  --format custom --compress 9 > keystone-$(date +%Y%m%d-%H%M%S).pgdump

Restore into an empty database with the three roles in place, then let the role bootstrap and the migrations run: the steps are in Backup and restore. Keep the keys from .env with the backup's recovery plan; the database is not usable without them.

Upgrade

See Upgrades and migrations for the full procedure. In short:

  1. Read the release notes for steps the release requires before it starts.
  2. Back up the database.
  3. Pull or build the new version and restart: docker compose up -d. The migrate service applies new migrations first.

Stop

docker compose down

docker compose down -v also deletes the PostgreSQL volume and every record in it.

Troubleshooting

More symptoms and fixes are in Troubleshooting.

SymptomLikely causeFix
Compose refuses to start and names a variableA required variable is emptySet it as shown under Configure
server exits at startA missing or placeholder keyRead docker compose logs server for the variable name
Sign-in fails with 500No OIDC private keySet NI_AUTH_OIDC_PRIVATE_KEY_FILE or NI_AUTH_OIDC_PRIVATE_KEY_PEM
Token validation failsIssuer mismatchMake NI_AUTH_JWT_ISSUER match NI_AUTH_PUBLIC_URL exactly
Email is not deliveredSMTP not configuredCheck the NI_AUTH_SMTP_* variables; use Mailpit locally

Last reviewed 17 October 2026View as Markdown

Docker Compose · Docs · Neural Inverse