Skip to content

Self-hosting

Deployment guide (step by step)

A step-by-step walkthrough to deploy self-hosted Keyright into your own environment: prerequisites, get the deploy kit, generate secrets, prepare PostgreSQL, choose a deploy path (Docker Compose, Kubernetes/Helm, or Windows/IIS), install your license, verify, and sign in — from zero to a running, licensed instance.

This is the linear, do-this-then-that guide to a running instance. It assumes you’ve skimmed the architecture overview. Every setting it touches has a full reference in Configuration; when a step needs more depth it links there.

By the end you’ll have a licensed Keyright instance answering /health, a signed-in dashboard, and the exact base URL your product’s SDK will point at.

Step 0 — Check the prerequisites

  • A container runtime (recommended): Docker 24+ with Compose v2, or Kubernetes 1.24+ with Helm 3. Or Windows/IIS with the .NET 8 Hosting Bundle (Step 4C, no containers).
  • PostgreSQL 14+ (16 recommended), UTF-8. For a first evaluation you can let the bundle run one for you (Step 3); for production, prepare your own managed/HA Postgres.
  • A Keyright license or trial key from Delta1 — required to run. Contact sales for a 30-day Enterprise trial or a paid Standard/Enterprise license. Save the key you receive as license.json.
  • openssl on the machine you run these steps from (for generating secrets in Step 2).

Step 1 — Get the deploy kit

The container image is public — pull it with no auth (pin the version for a reproducible install):

docker pull ghcr.io/delta1-labs/keyright:2.2.2     # or :latest

The deploy kit — the config files the rest of this guide references — is sent to you by Delta1 with your license (or on request from sales). It contains:

  • deploy/docker-compose.yml + deploy/.env.example (Docker Compose)
  • deploy/helm/keyright/ (Kubernetes / Helm)
  • deploy/windows/ (Windows / IIS)

Unpack it into a working directory; the commands below are run from there.

Step 2 — Generate your secrets

Keyright needs three secrets you create and keep. Generate them now:

openssl rand -hex 24                                                            # KEYRIGHT_ADMIN_TOKEN  (your root admin credential)
openssl rand -base64 32                                                         # KEYRIGHT_KEK          (encrypts signing keys at rest — 32 bytes)
openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt -outform DER | base64 -w0   # KEYRIGHT_SIGNING_KEY  (optional; signs offline leases)

(On macOS/BSD, base64 has no -w0; use ... -outform DER | base64 | tr -d '\n'.)

Back up the KEK and signing key separately and securely. They’re yours — we never see them — and without the KEK, the encrypted signing keys in your database cannot be recovered. The signing key is optional (Keyright generates a per-tenant one in the DB if you skip it), but if you set it, back it up too.

Full meaning of each variable is in Configuration.

Step 3 — Prepare the database

You provide a PostgreSQL server and an empty database; Keyright builds the schema itself on first start — there is no .sql file and no SQL to run by hand. Migrations are compiled into the image and applied automatically on every boot (db.Database.Migrate()).

Production — your own Postgres. Create an empty database and a login role, and make the role the database owner (the simplest correct setup — the app must be able to run CREATE/ALTER/DROP to apply migrations):

CREATE DATABASE keyright ENCODING 'UTF8' TEMPLATE template0;
CREATE ROLE keyright_app LOGIN PASSWORD 'a-strong-password';
ALTER DATABASE keyright OWNER TO keyright_app;

On PostgreSQL 15+, if the role is not the owner, also GRANT ALL ON SCHEMA public TO keyright_app;. Some managed providers (Azure Flexible Server, Cloud SQL) don’t allow CREATE DATABASE over SQL — create the empty database + role from their console/CLI, then grant as above. AWS RDS/Aurora, Cloud SQL, Azure, Neon, Supabase all work. Then build your connection string (Npgsql format, TLS required):

Host=db.internal.example.com;Port=5432;Database=keyright;Username=keyright_app;Password=...;SSL Mode=Require;Trust Server Certificate=true

That string is your KEYRIGHT_DB. See Configuration → Database for TLS options and managed-provider notes.

Evaluation — skip this step. Docker Compose and the Helm chart can start a postgres:16 for you with the database and role pre-created. Fine for a first run and small single-node installs; move to your own managed/HA Postgres for production.

Step 4 — Deploy

Pick the path that matches your environment. All three produce the same running service.

Step 4A — Docker Compose (single node)

The fastest way to a running instance — Postgres + the app on one host:

cp deploy/.env.example deploy/.env        # then fill in the secrets from Step 2 (and KEYRIGHT_DB for production)
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d

Set your license in deploy/.env as KEYRIGHT_SELF_LICENSE (the license JSON on one line) or mount a file and set KEYRIGHT_SELF_LICENSE_FILE (the kit has a commented volumes: example). Pin a version with KEYRIGHT_VERSION. For production set a real KEYRIGHT_DB rather than the bundled Postgres.

Step 4B — Kubernetes (Helm)

Save your license to license.json and pass it with --set-file — do not use --set/--set-string for the license or the DB string: both contain commas/braces that Helm’s --set parser splits on, which fails with key "…" has no value.

Production — your own external Postgres (recommended):

helm upgrade --install keyright deploy/helm/keyright -n keyright --create-namespace \
  --set image.repository=ghcr.io/delta1-labs/keyright \
  --set-string secrets.adminToken="$KEYRIGHT_ADMIN_TOKEN" \
  --set-string secrets.kek="$KEYRIGHT_KEK" \
  --set-file  secrets.selfLicense=license.json \
  --set postgres.enabled=false \
  --set-string externalDatabase.connectionString="Host=db.example.com;Port=5432;Database=keyright;Username=keyright_app;Password=...;SSL Mode=Require;Trust Server Certificate=true" \
  --set replicaCount=2

Evaluation — bundled in-cluster Postgres (drop the two DB lines above and keep the default):

helm upgrade --install keyright deploy/helm/keyright -n keyright --create-namespace \
  --set image.repository=ghcr.io/delta1-labs/keyright \
  --set image.tag=2.2.2 \
  --set-string secrets.adminToken="$KEYRIGHT_ADMIN_TOKEN" \
  --set-string secrets.kek="$KEYRIGHT_KEK" \
  --set-file  secrets.selfLicense=license.json

Pin the image version. Add --set image.tag=<version> (e.g. --set image.tag=2.2.2) for a reproducible deploy. Unset, it defaults to the chart’s appVersion.

Docker Hub rate limits? The bundled Postgres pulls postgres:16 from Docker Hub, which rate-limits anonymous pulls. If the postgres pod is stuck ImagePullBackOff, point it at your own registry/mirror with --set postgres.image=<registry>/postgres:16 (see Configuration → Kubernetes values).

Expect a few restarts on the first bundled-Postgres boot. Keyright runs its migrations at startup, and the bundled Postgres takes a moment to become Ready — so on a fresh evaluation install the app pod may CrashLoopBackOff a few times while it waits for the DB. This is expected, not an error; it clears within a minute or two once Postgres is Ready. With an external Postgres, a persistent crash loop instead means the DB genuinely isn’t reachable (see Operations → Troubleshooting).

secrets.adminToken and secrets.kek are required — the chart refuses to render without them. See Configuration → Kubernetes for ingress, replicas, resources, white-label, the bundled-image override, and reaching the service with a port-forward.

Step 4C — Windows / IIS

For shops that won’t run containers:

pwsh deploy/windows/build-win-bundle.ps1        # add -SelfContained if the server has no .NET 8

Unzip .artifacts/keyright-win/keyright-issuing-win-x64.zip into an IIS site’s physical path. The bundled web.config wires the ASP.NET Core Module (in-process). Set the KEYRIGHT_* values as machine environment variables (preferred) or in web.config, install the .NET 8 Hosting Bundle, and start the site. Point KEYRIGHT_DB at your PostgreSQL.

Step 5 — Verify

curl -fsS http://localhost:8080/health                                        # {"status":"ok","service":"keyright-issuing","version":"2.2.2"}
curl -H "X-Admin-Token: $KEYRIGHT_ADMIN_TOKEN" http://localhost:8080/admin/self-license

/health is liveness; /health/ready also checks the database. /admin/self-license reports your edition, phase (active / expiring / grace / gated), days remaining, and whether the runtime is blocked — if it says gated, your license didn’t load (see Licensing).

Your base URL. For a local/evaluation run it’s http://localhost:<KEYRIGHT_PORT> (default http://localhost:8080); in production it’s whatever host your load balancer serves (e.g. https://licensing.acme.example). Every curl and every SDK ServiceUrl from here on uses that base.

On Kubernetes without an ingress, reach the service with a port-forward. The service is named <release>-keyright — for the release keyright used above that’s keyright-keyright:

kubectl port-forward -n keyright svc/keyright-keyright 8080:8080

Step 6 — Sign in to the dashboard

Open <base>/dashboard. On a fresh instance you have no dashboard user yet — sign in with your admin token: expand “Advanced: use an access token”, paste your KEYRIGHT_ADMIN_TOKEN, then View dashboard. (Email/password sign-in needs a user provisioned via POST /admin/team/{id}/set-password; SSO is the Enterprise KEYRIGHT_SSO_* option.) The admin token is your root credential — treat it accordingly.

You’re running — what’s next

  • Wire your product to your instance — create a product, generate its signing key, point your app’s SDK at your base URL, and issue your first license. This is the step that makes self-hosting real.
  • Licensing & editions — how your own key gates the instance, the trial, and renewals.
  • Operations & troubleshooting — scaling, backups, upgrades, air-gapped, and the fix for anything that didn’t come up green above.

Stuck on a step? Talk to sales/support — we’ll get you running.