HealkiteDocs

Self-hosting

Run Healkite on your own infrastructure: one container with the API, the dashboard and the update CDN. Data never leaves your servers, and there are no usage limits.

Requirements

  • Docker (or Node 22.13+ without Docker)
  • A public HTTPS URL for the server, for example https://ota.example.com, behind your load balancer or a reverse proxy such as Caddy or nginx
  • 512 MB RAM and some disk for bundles (or an S3-compatible bucket)

Start the server

Self-hosting licenses come with access to the Healkite source repository. From a checkout:

sh
cd healkite
PUBLIC_URL=https://ota.example.com ADMIN_TOKEN=$(openssl rand -hex 24) docker compose up -d --build

On first start the server creates its manifest signing key and secret in the /data volume, next to the SQLite database and bundles. Back up the whole volume; losing server-keys.json means apps with the old manifest key need a store update.

Create your organization

sh
curl -X POST https://ota.example.com/v1/bootstrap \
  -H "x-admin-token: <ADMIN_TOKEN>" -H "content-type: application/json" \
  -d '{"orgName":"Acme"}'
# → { "token": "hk_…" }  an owner access key

Then remove ADMIN_TOKEN from the environment and restart. Use the token with the CLI:

sh
npx healkite login --api-url https://ota.example.com --token hk_…

Dashboard sign-in

Create a GitHub OAuth app with the callback URL https://ota.example.com/v1/auth/github/callback, set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET, restart, and invite people:

sh
npx healkite members add <github-login> --role owner

Open https://ota.example.com and sign in. For Okta, Microsoft Entra ID or Google Workspace, see SSO.

Point your apps at it

iOS Info.plistAndroid strings.xml
CDN URLHealkiteCdnUrl = https://ota.example.com/cdnhealkite_cdn_url
Manifest keyHealkiteManifestKey = manifestPublicKey from GET /v1/confighealkite_manifest_key

Configuration

VariableDefault
API_BASE_URLhttp://localhost:8787Public URL of the server
PORT8787
DATABASE_PATH/data/healkite.dbSQLite database
BLOB_DIR/data/cdnBundle storage when S3 isn't set
S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_REGIONStore bundles in S3, R2, MinIO or GCS (S3 API)
CDN_BASE_URL<API_BASE_URL>/cdnA CDN in front of /cdn or the bucket
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETDashboard sign-in
ADMIN_TOKENOne-time bootstrap
MANIFEST_SIGNING_KEY, MANIFEST_PUBLIC_KEY, AUTH_SECRETgeneratedBring your own keys instead
SIGNUP_MODEinviteopen lets anyone with GitHub create an org
GUARD_INTERVAL_SEC60How often guarded rollouts and schedules are evaluated

Scaling

The server is stateless apart from SQLite and the bundle directory. For heavy traffic, put a CDN in front of /cdn (manifests are cacheable for 60 seconds, bundles forever) and set CDN_BASE_URL. The same code also runs on Cloudflare Workers with D1 and R2, which is how Healkite Cloud runs.

Without Docker

sh
pnpm install && node scripts/build-selfhost.mjs
cd dist/selfhost && API_BASE_URL=https://ota.example.com DASHBOARD_DIR=dashboard \
  DATABASE_PATH=data/healkite.db BLOB_DIR=data/cdn node server.mjs

Updating

Pull the new version and run docker compose up -d --build. Database migrations run on start.