Installation
This page covers a production self-host install. For a quick local trial, see Quickstart.
Compose file anatomy
Okapi's bundled docker-compose.yml defines two services:
db—postgres:18-alpine. On a fresh volume,deploy/initdbscripts run automatically and createokapi_app, a non-superuser database role the app connects as.OKAPI_APP_DB_PASSWORDsets that role's password;POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DBare the bootstrap superuser credentials used only by thedbcontainer itself for initialization — the app never connects as that role.app— the Okapi binary, built from source (build: context: ., target: runtime) or, for a production deployment, pinned to a published image (see Updating below). Listens on:8080inside the container, published to the host onOKAPI_APP_PORT(default8480).
Postgres data persists in the okapi-db named volume — back it up before every upgrade (see Backups).
The non-superuser database role requirement
Okapi's row-level security backstops every tenant-scoped query: every table carrying an organization_id enforces FORCE ROW LEVEL SECURITY, and a query with no organization bound sees zero rows. Superusers and roles with BYPASSRLS bypass this entirely — so OKAPI_DATABASE_URL must point at a non-superuser role.
If you're using the bundled compose stack, this is automatic: deploy/initdb creates okapi_app on a fresh database and the default OKAPI_DATABASE_URL already points at it. If you're pointing Okapi at an external Postgres instance, create a dedicated non-superuser role yourself (it must own the schema it migrates) and use that role's connection string — never a superuser DSN, even temporarily.
Environment variables
All configuration is via environment variables, all prefixed OKAPI_.
| Variable | Required | Default | Purpose |
|---|---|---|---|
OKAPI_DATABASE_URL |
yes | — | Postgres connection string. Must be a non-superuser role (see above). The bundled compose stack creates okapi_app automatically on a fresh database. |
OKAPI_BASE_URL |
yes | — | Public base URL; used for emailed links (invites, email verification, password reset), alert deep links, and as the trusted origin for dashboard (CSRF) requests. https://… also enables the Secure cookie flag. Must match the URL browsers use to reach Okapi. |
OKAPI_SECRET_KEY |
yes | — | Server-side HMAC key for signed links/tokens. Use a long random value, e.g. openssl rand -hex 32. |
OKAPI_LISTEN_ADDR |
no | :8080 |
Address the server listens on (inside the container). |
OKAPI_SETUP_TOKEN |
no | — | If set, first-run setup requires this token in the X-Okapi-Setup-Token header. Recommended in production; if unset, Okapi auto-generates one and logs the setup URL. |
OKAPI_EVENT_RETENTION_DAYS |
no | 90 |
Instance default for event retention; a per-organization entitlement or per-project override wins when set. 0 disables pruning entirely. |
OKAPI_LOG_RETENTION_DAYS |
no | 30 |
Instance default for log retention; a per-organization entitlement wins when set. 0 disables pruning entirely. |
OKAPI_INGEST_RATE_LIMIT |
no | 200 |
Instance default ingest rate (events/sec, token bucket per DSN key); a per-organization entitlement overrides it. |
OKAPI_QUOTA_OVERAGE_PERCENT |
no | 150 |
Soft-overage ceiling as a percent of an organization's daily quota — see Quotas & usage. |
OKAPI_INGEST_QUEUE_LIMIT |
no | 0 |
Shed ingest with a 429 when the pending queue backlog reaches this depth (0 disables). Spike protection against a flood or a stalled worker. |
OKAPI_DB_MAX_CONNS |
no | pgx default (max(4, NumCPU)) |
Max Postgres pool connections. Set a bound for small VPSes or when packing many instances onto one database. |
OKAPI_SMTP_HOST |
no | — | SMTP host; enables outgoing email (alert notifications, invite emails, email verification, password reset) when set. Without it, invites fall back to a copyable link and administrators can generate password-reset links from Instance settings. |
OKAPI_SMTP_PORT |
no | 587 |
SMTP port. |
OKAPI_SMTP_USER |
no | — | SMTP username (enables PLAIN auth when set). |
OKAPI_SMTP_PASSWORD |
no | — | SMTP password. |
OKAPI_SMTP_FROM |
no | okapi@localhost |
From address for outgoing email. |
OKAPI_SKIP_MIGRATE |
no | unset | If set, serve skips auto-migration (run okapi migrate yourself). This also bypasses the license migration gate below — managing migrations yourself is the same explicit consent as OKAPI_FORCE_MIGRATE. |
OKAPI_UPDATE_ENDPOINT |
no | https://api.useokapi.app |
Base URL Okapi polls for the latest version, and — when licensed — for license check-ins. |
OKAPI_DISABLE_TELEMETRY |
no | unset | Set to 1 to stop sending anonymous usage telemetry. The version check still runs. See Telemetry. |
OKAPI_ORGANIZATION_NAME |
no | Default |
Name of the default organization created by createadmin. The interactive first-run setup screen collects the organization name directly instead. |
OKAPI_MULTI_ORGANIZATION |
no | false |
Enables cloud multi-organization chrome (self-serve signup, organization creation, the organization switcher). Leave unset on self-host. |
OKAPI_FORCE_MIGRATE |
no | unset | Explicit consent to migrate past the license migration gate (see below) regardless of coverage. |
The createadmin command additionally reads OKAPI_ADMIN_EMAIL, OKAPI_ADMIN_PASSWORD (≥8 characters), and OKAPI_ADMIN_NAME to create a superadmin non-interactively.
Two more variables live at the compose level (read by the db container and the compose file itself, not by the Okapi binary): OKAPI_APP_PORT (host port for the app, default 8480) and OKAPI_APP_DB_PASSWORD (the okapi_app role's password — see above). POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB configure the bootstrap superuser the db container starts with; the app never connects as that role.
First-run setup
- Override the defaults from the base
docker-compose.yml— at minimum a strongOKAPI_SECRET_KEY, your realOKAPI_BASE_URL(https://…), and a strongOKAPI_APP_DB_PASSWORD. Copy.env.exampleto.envand adjust, or layer your own compose override. - Generate a secret:
openssl rand -base64 32. - Complete first-run setup: on first boot Okapi logs a ready line with the full setup URL (token included). Open it to create the owner account and your organization. Reprint it anytime with
docker compose exec app /okapi setup-url. To pin a fixed token instead of relying on auto-generation, setOKAPI_SETUP_TOKEN. - Terminate TLS at a reverse proxy (Caddy, nginx, Traefik) in front of the app and set
OKAPI_BASE_URLto thehttps://URL. - Back up the Postgres volume (
okapi-db) — it holds every project, issue, and event.
Updating
Okapi publishes versioned images (ghcr.io/useokapi/okapi:<version>). For a production deployment, pin the app service to an explicit version instead of building from source:
services:
app:
image: ghcr.io/useokapi/okapi:1.8.2 # example — use the version you're deploying
# ...same environment/ports as before
okapi serve auto-migrates the schema on boot unless OKAPI_SKIP_MIGRATE is set. A licensed, self-hosted instance is protected by a migration gate: if the running build is newer than your license's coverage window (updatesUntil) and that upgrade has pending schema migrations, boot holds in a static limbo page instead of migrating — the schema stays untouched and the instance stops accepting new events until you act. The limbo page offers two paths:
- Renew — completing a checkout unlocks and resumes boot live, no restart needed.
- Roll back — redeploy the newest version your license covers. The exact compose image pin to use is shown on the page (the same
image: ghcr.io/useokapi/okapi:X.Y.Zformat above).
Every other state migrates and serves normally: a fresh database, a dev/CI build, an unlicensed (trial) instance, an upgrade within your coverage window, or an out-of-coverage upgrade with no pending migrations at all. OKAPI_FORCE_MIGRATE=1 migrates through the gate anyway — an explicit operator override. See Licensing for the full coverage model.
Always back up before upgrading — see below. Upgrading within coverage, or with no pending migrations, is never blocked, but a backup costs you nothing and a bad migration is not something you want to discover after the fact.
Backups
Back up the Postgres volume (okapi-db) — it holds all projects, issues, and events. A standard pg_dump/pg_basebackup against the db service (or your external Postgres instance, if you're using one) works with no Okapi-specific steps; there's no separate metadata store to snapshot. Take a backup immediately before every upgrade, and on whatever recurring schedule your data is worth.
Next: SDK setup to start sending events.