Self-hosting

OtaKit is fully open source and can run on your own infrastructure. The managed service at otakit.app runs everything for you — self-hosting is the advanced path.

What you deploy

  • Public site (packages/site) — landing page, docs, contact, and legal pages.
  • Console (packages/console) — Next.js control plane: auth, dashboard UI, API routes, billing, and Prisma migrations.
  • Ingest Worker (packages/ingest) — Cloudflare Worker that receives device events and writes them to Tinybird. Required if you want to use dashboard analytics.
  • CDN bucket — public R2 or S3 bucket with a CDN domain. Serves manifest files and bundle zips directly to devices.

The CLI (packages/cli) and Capacitor plugin (packages/capacitor-plugin) are client-side tools — they can be configured to point at your self-hosted services.

Required services

  • PostgreSQL 14+
  • S3-compatible object storage (Cloudflare R2, AWS S3)
  • A public CDN domain in front of the storage bucket
  • At least one provider (Google, Apple, Github, or Email OTP via Resend) for sign-in

Environment variables

Dashboard — required

DATABASE_URL=postgresql://user:pass@localhost:5432/otakit

BETTER_AUTH_SECRET=your-random-secret    # openssl rand -hex 32
BETTER_AUTH_URL=https://your-domain.com
# At least one provider for sign-in
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=....
RESEND_API_KEY=...
EMAIL_FROM=...

R2_BUCKET=...
R2_ACCESS_KEY=...
R2_SECRET_KEY=...
R2_ENDPOINT=https://....r2.cloudflarestorage.com
CDN_BASE_URL=https://cdn.your-domain.com

# Cloudflare CDN purge — instant cache invalidation after releases.
# Without this, stale manifests may be served until CDN TTL expires.
CF_ZONE_ID=...
CF_API_TOKEN=...

# Tinybird — device event analytics and dashboard counts.
# Without this, the dashboard shows empty analytics and download counts return 0.
TINYBIRD_API_HOST=https://api.tinybird.co
TINYBIRD_READ_TOKEN=...

# Manifest signing — ES256 signatures on manifest JSON.
# Generate with: otakit generate-signing-key
MANIFEST_SIGNING_KID=key-2026-01
MANIFEST_SIGNING_KEY=-----BEGIN EC PRIVATE KEY-----...
# Set MANIFEST_SIGNING_DISABLED=true to skip signing entirely.

Ingest Worker

Only needed if you want to use analytics. See packages/ingest/wrangler.jsonc and packages/ingest/.env.example for the full config. The Worker needs a Tinybird append token and a Cloudflare Queue.

Deploy

Public site

git clone https://github.com/OtaKit/otakit
cd otakit
pnpm install

cd packages/site
pnpm build
pnpm start

Console

git clone https://github.com/OtaKit/otakit
cd otakit
pnpm install

cd packages/console
npx prisma migrate deploy
pnpm build
pnpm start

Runs on port 3000. Put a reverse proxy (nginx, Caddy) in front with HTTPS.

Ingest Worker

cd packages/ingest
npx wrangler deploy

Tinybird project

cd tinybird
tb login
tb deploy

Manifest signing

otakit generate-signing-key

Add the private key to the dashboard env (MANIFEST_SIGNING_KID, MANIFEST_SIGNING_KEY). Add the public key to the Capacitor plugin config (manifestKeys).

Configure the plugin

plugins: {
  OtaKit: {
    appId: "YOUR_OTAKIT_APP_ID",
    cdnUrl: "https://cdn.your-domain.com",
    ingestUrl: "https://ingest.your-domain.com/v1",  // omit if not using Tinybird
    manifestKeys: [
      { kid: "key-2026-01", key: "MFkwEwYH..." }
    ]
  }
}

Configure the CLI

export OTAKIT_SERVER_URL=https://your-domain.com/api/v1
export OTAKIT_TOKEN=otakit_sk_...

Or set serverUrl in the Capacitor plugin config so the CLI can read it automatically.