Skip to content

Configuration ​

Configuration is read from environment variables and validated at boot. Missing required values stop startup before migrations or a listener bind.

VariableDefaultRequiredNotes
ENV_MODEproductionnoproduction, development, or test
OPENVAULT_BASE_URL—yesAbsolute HTTPS URL; no credentials, query, or fragment
OPENVAULT_ADMIN_PORT8443noCanonical admin HTTPS port
PORT—noCompatibility alias for OPENVAULT_ADMIN_PORT; a conflict is an error
OPENVAULT_AGENT_PORT8444noAgent TLS port; join is token-authenticated, other routes are mTLS; must differ from the admin port
OPENVAULT_DB_URL—one URL formRuntime database connection string; the server connects as openvault_app
OPENVAULT_DB_URL_FILE—one URL formRead the runtime URL from a regular file that is not group- or world-writable; Docker secrets are 0444 and work
OPENVAULT_MIGRATION_DB_URLnonenoMigrator URL used for startup DDL; must target the same host, port, and database
OPENVAULT_MIGRATION_DB_URL_FILEnonenoFile form of the migrator URL
OPENVAULT_KEYRING—yesAbsolute path to a regular file with mode 0600
OPENVAULT_TLS_CERT—yesReadable server certificate PEM
OPENVAULT_TLS_KEY—yesReadable private-key PEM that matches the certificate
OPENVAULT_INTERNAL_CA—yesReadable public CA certificate; the private key is wrapped in the database
OPENVAULT_OIDC_ISSUERnonenoHTTPS URL; enables OIDC
OPENVAULT_OIDC_CLIENT_ID / _SECRETnonewith OIDCNon-empty when an issuer is set
OPENVAULT_OIDC_REDIRECT_URL${BASE_URL}/v1/auth/callbacknoHTTPS URL
OPENVAULT_OIDC_ALLOWED_DOMAINSnonenoComma-separated domain allowlist
OPENVAULT_OIDC_ENDPOINT_ORIGINSissuer originnoComma-separated HTTPS origins allowed for authorization, token, JWKS, and UserInfo endpoints
OPENVAULT_SECRET_MAX_BYTES65536no1024–65536; decoded value limit enforced by the secret routes
OPENVAULT_AUTH_DB_URLapp URL in development/testproductionDedicated openvault_auth database URL for control-plane writes
OPENVAULT_AUTH_DB_URL_FILEnonenoFile form of the auth URL
OPENVAULT_BACKUP_DB_URLnonefor ov backupDedicated read-only openvault_backup URL used for the logical dump
OPENVAULT_BACKUP_DB_URL_FILEnonenoFile form of the backup URL
OPENVAULT_PURGE_DB_URLnonenoDedicated openvault_purger database URL used only by secret and certificate purge
OPENVAULT_PURGE_DB_URL_FILEnonenoFile form of the purge URL
OPENVAULT_CERT_MAX_BYTES131072no1024–131072; combined public chain bound; the private key is separately limited to 16,384 bytes
OPENVAULT_CERT_EXPIRY_HOUR3no0–23 UTC; daily expiry-scan hour
OPENVAULT_CERT_EXPIRY_WEBHOOK_URLnonenoHTTPS URL (loopback HTTP outside production); credentials, fragments, and production link-local/metadata targets are rejected
OPENVAULT_CERT_EXPIRY_WEBHOOK_SECRETnoneno16–256 characters; adds X-OpenVault-Timestamp and an HMAC signature; never logged or redacted-exposed
OPENVAULT_SOFT_DELETE_DAYS30no7–90
OPENVAULT_AUDIT_RETENTION_DAYS180no30–3650
OPENVAULT_JOIN_TOKEN_TTL15mno1m–24h; default join-token lifetime; an admin request may override it up to 24 hours
OPENVAULT_CERT_TTL_DAYS30no1–90; workload certificate lifetime; renewal is at two thirds of the issued lifetime
OPENVAULT_SESSION_TTL_HOURS8no1–24
OPENVAULT_LOG_LEVELinfonoerror, warn, info, or debug
OPENVAULT_TRUST_PROXYtruenoTrust forwarded headers only from peers in OPENVAULT_PROXY_CIDRS
OPENVAULT_RUN_MIGRATIONStruenoRun startup migrations; set false when a one-shot init job already migrated
OPENVAULT_PROXY_CIDRS127.0.0.1/32,::1/128noComma-separated IPv4/IPv6 CIDRs; set the proxy address explicitly in a multi-container deployment

OPENVAULT_ADMIN_PORT wins over PORT. If both are set and differ, startup fails with a keyed error. The agent port must differ from the resolved admin port.

OPENVAULT_DB_URL_FILE, OPENVAULT_MIGRATION_DB_URL_FILE, OPENVAULT_AUTH_DB_URL_FILE, OPENVAULT_BACKUP_DB_URL_FILE, and OPENVAULT_PURGE_DB_URL_FILE read the URL from a file instead of the environment. The file must be a regular file and must not be group- or world-writable; Docker secrets mount as 0444. Setting both the direct and file forms is an error. The migrator, auth, backup, and purge URLs must target the same host, port, and database as OPENVAULT_DB_URL; they may use a different role.

OPENVAULT_SESSION_TTL_HOURS is an absolute lifetime measured from session creation, not a sliding idle timeout. The auth URL must target the same database as OPENVAULT_DB_URL. OPENVAULT_OIDC_ALLOWED_DOMAINS enables JIT provisioning; without it, OIDC identities must already be linked. JIT also requires email_verified: true and an exact domain match.

CLI and bootstrap ​

The ov client reads its own connection settings. These variables are separate from the server process. ov config show --redacted validates the server environment with the same loader and prints the support-safe view; attach that output to a support request instead of the raw environment.

VariableDefaultRequiredNotes
OPENVAULT_URLnonefor ov login and ov doctorHTTPS origin of the public API; no credentials, path, query, or fragment
OPENVAULT_CA_FILEnonerecommendedPEM bundle that verifies the server; must match the proxy's certificate chain
OPENVAULT_BACKUP_PASSPHRASE_FILEnonefor non-interactive backup/restore0600 file with the archive passphrase; at least 12 characters
OPENVAULT_RESTORE_DB_URLOPENVAULT_MIGRATION_DB_URL or OPENVAULT_DB_URLfor restoreOwner/migrator URL used to disable triggers, truncate, and load rows
OPENVAULT_RESTORE_DB_URL_FILEnonefor restoreFile form of the restore URL
OPENVAULT_RESTORE_AGENT_CERTIFICATEnonenoPre-backup agent certificate PEM verified after restore
OPENVAULT_BACKUP_DIRnonefor ov doctorDirectory checked against the 24-hour RPO
OPENVAULT_WEB_ROOTbundled UI directorynoDirectory containing the built web UI; when absent the server runs API-only

The bootstrap command ov init reads the server database and file settings from the environment. It never accepts the database URL as a command-line option, because a database URL usually contains a password.

VariableDefaultRequiredNotes
OPENVAULT_DB_URLnoneyesApplication runtime URL; the app role cannot perform bootstrap writes
OPENVAULT_MIGRATION_DB_URLOPENVAULT_DB_URLfor ov initMigrator/owner URL used for DDL and bootstrap data writes; must target the same database
OPENVAULT_KEYRINGnoneyesAbsolute keyring path; created atomically with mode 0600 when absent
OPENVAULT_INTERNAL_CAderived from the TLS certificate directoryyesPublic internal CA path
OPENVAULT_CA_PUBLIC_DIRnonenoDirectory that receives ca.pem for the proxy
OPENVAULT_TLS_CERT / OPENVAULT_TLS_KEYnonefor TLS provisioningServer certificate and private-key PEM paths; both or neither
OPENVAULT_BASE_URLhttps://localhostnoHTTPS origin used for server-certificate SANs
OPENVAULT_ORG_NAMEOpenVaultnoOrganization display name
OPENVAULT_BOOTSTRAP_ADMIN_NAMEOpenVault AdministratornoAdministrator display name
OPENVAULT_BOOTSTRAP_ADMIN_EMAILadmin@openvault.localnoAdministrator email
OPENVAULT_BOOTSTRAP_PASSWORD_FILEnoneone password modeOperator-supplied 0600 password file
OPENVAULT_BOOTSTRAP_PASSWORD_OUTPUTbeside the keyringnoOne-time generated password path

ov init --prepare implies --generate-password and derives the paths from the environment; it is the reference Compose provisioner. The generated password is written once with mode 0600 and is never printed. Re-running init does not rotate the CA or replace an existing administrator password. If the keyring is missing but wrapped state exists, init fails closed and asks for a restore from backup.

Backup, restore, and KEK rotation ​

ov backup requires OPENVAULT_BACKUP_DB_URL for the read-only logical dump, OPENVAULT_DB_URL for the backup.create audit event, OPENVAULT_KEYRING, and OPENVAULT_INTERNAL_CA (or a ca.pem beside OPENVAULT_TLS_CERT). The dump connection is refused if it is the openvault_app or openvault_auth role or cannot read the control-plane tables. The archive is encrypted using the passphrase from --passphrase-file, OPENVAULT_BACKUP_PASSPHRASE_FILE, or an interactive hidden prompt. The v1.0 archive contains a logical row dump, the raw keyring, a redacted configuration manifest, the public CA, and audit checkpoints. It is a portable logical archive.

ov restore uses OPENVAULT_RESTORE_DB_URL for migrations and data loading. It must target the same database as OPENVAULT_DB_URL, and that role must own the tables so user triggers can be disabled. Stop the server during restore. The target must be empty unless --force is supplied, and --yes is required in a non-interactive session. The command verifies the archive checksums, every current secret and certificate private key, the session signing key, the audit chain, and the CA key before reporting success. --agent-certificate adds the pre-backup agent certificate check.

ov kek rotate also needs table ownership. Stop the server first: a running process holds the old keyring in memory and could write a row with the old key id after the CLI's verification. The command removes the old key only after every key table has been checked.

Web UI serving ​

The server serves the web UI when OPENVAULT_WEB_ROOT (or its bundled default) contains index.html and assets. Hashed assets are immutable for one year; index.html is not cached; unmatched GET HTML routes fall back to the web UI. Unknown API routes remain JSON 404s. If the build is absent the server starts normally and serves no UI.

The UI-enabled CSP allows same-origin scripts, connections, images, fonts, and inline style elements. Scripts are same-origin only and unsafe-eval is never allowed. API-only mode keeps the deny-by-default policy.

Audit maintenance ​

ov audit checkpoint --out <file> exports the newest checkpoint using OPENVAULT_DB_URL or OPENVAULT_DB_URL_FILE. It is safe to schedule and does not modify the chain. Pruning is a separate server-side command. It reads the same server configuration as the process and additionally reads:

VariableDefaultRequiredNotes
OPENVAULT_PRUNE_DB_URL—for audit:pruneDatabase URL for the dedicated openvault_pruner session

Deployment must create a login role named openvault_pruner and grant it EXECUTE on public.openvault_prune_audit_prefix(text). Do not grant table, sequence, column, UPDATE, TRUNCATE, or ownership privileges. The application verifies the retained chain, exports the prefix, writes the boundary checkpoint, and inserts a short-lived single-use authorization; the procedure consumes that authorization, re-checks the boundary and count, deletes the prefix, and writes a completion receipt in one transaction. A holder of the pruner credential alone cannot delete an audit prefix or write a checkpoint. The export file contains every tenant's events; treat the URL and file as high-value secrets. Run maintenance on the vault host, or supply the same database, keyring, TLS/CA, and base URL settings as the server. The prune command checks that its connection targets the same database that was verified and exported.

Body limits ​

The HTTP JSON parser has a 512 KiB hard ceiling. Per-path guards apply: 512 KiB for /v1/data* (sized for worst-case JSON escaping of a 64 KiB value), 128 KiB for other JSON under /v1, and a 64 KiB legacy guard for /v1/secrets*. OPENVAULT_SECRET_MAX_BYTES and OPENVAULT_CERT_MAX_BYTES are decoded product-value limits; the decoded bytes remain the authoritative check. Requests above the active ceiling return 413 payload_too_large, including chunked requests that do not carry Content-Length.

Requests with a non-JSON body under /v1 are rejected with 415.

Least-privilege database roles ​

RoleOwnershipPurpose
openvault_migratorDatabase, schema, tablesMigrations and restore
openvault_appNoneData-plane runtime DML and explicit reads
openvault_authNoneControl-plane authentication, RBAC, and agent-identity DML
openvault_backupNoneRead-only logical backup dump
openvault_purgerNoneControlled purge
openvault_prunerNoneControlled audit prune; EXECUTE on the prune procedure only

The server runs startup migrations with OPENVAULT_MIGRATION_DB_URL and then serves traffic with OPENVAULT_DB_URL. The runtime role cannot create tables or delete audit events. deploy/postgres/grants.sql asserts that the migrator owns every application table, so a drifted deployment fails loudly instead of silently granting the runtime role DDL. The app role has no access to principals, local_credentials, role_assignments, oidc_identities, cli_sessions, join_tokens, workload_certs, consumers, policy_state, or org; those are control-plane or credential tables. The app role reads consumer summaries through the restricted secret_consumer_summaries view. Re-run grants.sql after a migration that adds a table the runtime, purge, prune, or backup path must access. The openvault-roles job runs ensure-auth-role.sql and ensure-backup-role.sql on every deployment so an existing database also gets those roles.

Secret and certificate purge ​

Secret and certificate purge need a dedicated login role named openvault_purger, because the database trigger rejects deletes from the application role. The reference stack creates all six roles with deploy/postgres/roles.sql, makes openvault_migrator the database and table owner, and applies deploy/postgres/grants.sql. Set OPENVAULT_PURGE_DB_URL to that role's URL or point OPENVAULT_PURGE_DB_URL_FILE at a Docker secret. The equivalent grants are:

sql
GRANT SELECT ON projects TO openvault_purger;
GRANT SELECT, DELETE ON secrets TO openvault_purger;
GRANT SELECT, DELETE ON secret_versions TO openvault_purger;
GRANT SELECT, DELETE ON certificates TO openvault_purger;
GRANT SELECT, DELETE ON certificate_versions TO openvault_purger;
GRANT SELECT, DELETE ON certificate_expiry_notifications TO openvault_purger;

Do not make the role a superuser, a table owner, or a member of any owner role. The server verifies the database, session user, current user, superuser status, ownership, and role memberships on every purge. Without a valid URL the server still starts and only purge returns 503. The URL is a high-value secret and is not included in the redacted configuration or logs.

Certificate expiry ​

The expiry job runs once per UTC day at OPENVAULT_CERT_EXPIRY_HOUR. It considers only the current version of an active certificate in an active project and sends one warning per (version, 30/14/7-day threshold). Set OPENVAULT_CERT_EXPIRY_WEBHOOK_URL to deliver warnings; without it, warnings are logged and audited with webhook: "skipped".

The webhook receives public metadata only. With OPENVAULT_CERT_EXPIRY_WEBHOOK_SECRET, the request carries X-OpenVault-Timestamp and X-OpenVault-Signature: sha256=<HMAC-SHA256(secret, timestamp + "." + rawBody)>. A failed delivery is retried on a later run. The notification table makes the claim durable; a crash after a claim is recovered after a one-hour lease on a later run.

Keyring ​

The keyring is a file outside the database. The path is required and must be an absolute path to a regular file with exact mode 0600. The parent directory must be owned by the current user and must not be group- or world-writable; 0700 is used when init creates it. A group- or world-readable file, or a group- or world-writable directory, stops startup.

ov init creates the keyring file; ov backup, ov restore, and ov kek rotate consume and replace it.

Workload CA ​

The public CA certificate at OPENVAULT_INTERNAL_CA must match the CA key stored in the CA key. It is a wrapped key; it is never a file and is never returned. The deployment provisioner generates the CA once. Re-running the provisioner does not rotate it. Startup fails closed if the row is missing, the KEK cannot unwrap it, or it does not match the public certificate. /ready signs and verifies a short test certificate with the loaded key; it does not re-read the row. ov kek rotate re-wraps the CA key row; ov restore re-materializes the public certificate and verifies the restored CA key.

Readiness ​

EndpointSemantics
GET /healthLiveness only; remains 200 during a dependency outage
GET /readyDatabase, keyring, TLS/CA, CA signing key loaded at startup (including a sign/verify probe), and audit-table probes; 200 or 503 not_ready

/v1/ready is not part of the contract. The readiness database probe uses a 2-second connection timeout and a bounded outer timeout.

Proxy trust ​

Only set OPENVAULT_TRUST_PROXY=true when the server is reachable exclusively through the proxy. The reference Compose stack uses a dedicated admin_net and a single pinned proxy address:

text
OPENVAULT_PROXY_CIDRS=172.30.0.2/32

Forwarded headers from any other source are ignored. Loopback peers may call only /health and /ready so container health checks work.

TLS and headers ​

The admin listener is HTTPS only and uses TLS 1.2 or newer. The agent listener accepts a client certificate chained to OPENVAULT_INTERNAL_CA on every route except POST /v1/agent/join; join is token-authenticated. The server does not own the public HSTS contract; Caddy sets HSTS and the public client/upstream timeouts.

Agent configuration ​

The ov-agent binary reads its own environment. These variables are separate from the server's configuration.

VariableDefaultRequiredNotes
OPENVAULT_URL—yesHTTPS origin of the agent listener; no credentials, path, query, or fragment
OPENVAULT_CA_FILE—yesPEM bundle that verifies the vault server
OPENVAULT_JOIN_TOKENnonefirst startOne-time 43-character base64url token; not stored
OPENVAULT_PROJECT—yesOpenVault project name
OPENVAULT_SERVICE—yesService identity name
COMPOSE_PROJECT_NAME—yesCompose project name
OPENVAULT_MANIFEST/etc/openvault/manifest.yamlnoAbsolute manifest path
OPENVAULT_OUTPUT_DIR/run/secretsnoAbsolute output root; the manifest must equal it
OPENVAULT_STATE_DIR/var/lib/openvaultnoAbsolute state directory; mode 0700
OPENVAULT_STATUS_FILE/run/openvault/status.jsonnoAbsolute value-free status file
OPENVAULT_SOCKET_PATH/run/openvault/agent.socknoAbsolute read-only unix socket

The manifest project, service, and output_dir must equal the environment. refresh is 30s–24h (default 300s), and each entry has a safe file or env target plus an optional mode and optional flag. ov-agent health reads the status file; --ready is the Compose health gate and exits 0 when every required entry is delivered.

Released under the Apache-2.0 License