Okapi

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:

  • dbpostgres:18-alpine. On a fresh volume, deploy/initdb scripts run automatically and create okapi_app, a non-superuser database role the app connects as. OKAPI_APP_DB_PASSWORD sets that role's password; POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB are the bootstrap superuser credentials used only by the db container 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 :8080 inside the container, published to the host on OKAPI_APP_PORT (default 8480).

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

  1. Override the defaults from the base docker-compose.yml — at minimum a strong OKAPI_SECRET_KEY, your real OKAPI_BASE_URL (https://…), and a strong OKAPI_APP_DB_PASSWORD. Copy .env.example to .env and adjust, or layer your own compose override.
  2. Generate a secret: openssl rand -base64 32.
  3. 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, set OKAPI_SETUP_TOKEN.
  4. Terminate TLS at a reverse proxy (Caddy, nginx, Traefik) in front of the app and set OKAPI_BASE_URL to the https:// URL.
  5. 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.Z format 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.