Self-hosting (Advanced)

OtaKit is fully open source and can run on your own infrastructure. You'll need PostgreSQL and any S3-compatible object storage (AWS S3, Cloudflare R2, MinIO).

If you want the managed OtaKit service, use the standard hosted setup guide instead.

Requirements

  • Node.js 23+
  • PostgreSQL 14+
  • S3-compatible storage (R2, MinIO, AWS S3)

Environment variables

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

# Auth
BETTER_AUTH_SECRET=your-random-secret    # openssl rand -hex 32
BETTER_AUTH_URL=https://your-domain.com

# S3-compatible storage
R2_BUCKET=otakit-bundles
R2_ACCESS_KEY=...
R2_SECRET_KEY=...
R2_ENDPOINT=https://....r2.cloudflarestorage.com

# Optional: upload size limit (bytes, default 200MB)
MAX_BUNDLE_SIZE=209715200

# Manifest signing is enabled by default
# Generate with: otakit generate-signing-key
MANIFEST_SIGNING_KID=key-2026-01
MANIFEST_SIGNING_KEY=-----BEGIN EC PRIVATE KEY-----...

# Only set this if you intentionally want unsigned manifests
# MANIFEST_SIGNING_DISABLED=true

# Optional: global admin key for organization management
ADMIN_SECRET_KEY=your-admin-key

Deploy

git clone https://github.com/nicepkg/otakit
cd otakit

# Install dependencies
pnpm install

# Run database migrations
cd packages/web
npx prisma migrate deploy

# Build and start
pnpm build
pnpm start

The server runs on port 3000 by default. Point your reverse proxy (nginx, Caddy) to it and ensure HTTPS is configured.

Docker

docker run -d \
  -p 3000:3000 \
  -e DATABASE_URL=postgresql://... \
  -e BETTER_AUTH_SECRET=... \
  -e BETTER_AUTH_URL=https://your-domain.com \
  -e R2_BUCKET=otakit-bundles \
  -e R2_ACCESS_KEY=... \
  -e R2_SECRET_KEY=... \
  -e R2_ENDPOINT=https://... \
  ghcr.io/nicepkg/otakit:latest

Initial setup

  1. Open your domain in a browser and sign in — the first user account is created automatically via email OTP.
  2. Create an organization from the Account tab.
  3. Generate an API key from the Organization tab — you'll need this for the CLI.
  4. Point the CLI to your server:
export OTAKIT_SERVER_URL=https://your-domain.com/api/v1
export OTAKIT_SECRET_KEY=otakit_sk_...

Manifest signing

Manifest signing is enabled by default. For a normal self-hosted setup, generate an ES256 key pair and set these on the server:

otakit generate-signing-key

This outputs the server environment variables (MANIFEST_SIGNING_KID, MANIFEST_SIGNING_KEY) and the plugin config (manifestKeys). Add them to your server and Capacitor config respectively.

plugins: {
  OtaKit: {
    serverUrl: "https://your-domain.com/api/v1",
    appId: "YOUR_OTAKIT_APP_ID",
    manifestKeys: [
      { kid: "key-2026-01", key: "MFkwEwYH..." }
    ]
  }
}

Keep the private signing key on the server only. The plugin should only receive the public verification keys in manifestKeys.

If you intentionally want unsigned manifests on a custom server, set MANIFEST_SIGNING_DISABLED=true. When signing is not disabled, missing signing env vars are treated as a server misconfiguration and manifest requests will fail.

Connecting CLI and plugin

Point the CLI to your server with OTAKIT_SERVER_URL:

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

In your Capacitor plugin config, set serverUrl to your server:

plugins: {
  OtaKit: {
    serverUrl: "https://your-domain.com/api/v1",
    appId: "YOUR_OTAKIT_APP_ID",
    // manifestKeys: [{ kid, key }]
  }
}

The serverUrl is only needed for self-hosting — it defaults to https://otakit.app/api/v1 when omitted. Follow the standard setup guide for the rest of the plugin and CLI configuration.