Skip to content

License a deployment

A deployment key is a machine credential that lets a server fetch its own licence, daily, for as long as your subscription is live. Configure it once and there is no renewal step for anyone to perform.

This page issues one, puts it in place for both deployment shapes, and then covers what each licensing state looks like from your side.

Read How licensing works first if you want the reasoning. This page is the procedure.

  • A deployment from either Docker Compose or Helm.
  • An account at account.bugb.io with a subscription.
  • A bearer token for the server, so you can read GET /v1/license back. On a deployment that is not yet licensed you cannot mint one, because minting is a write: license it first, then mint.
Variable What it is
BUGB_DEPLOYMENT_KEY The one to set. A machine credential, bugb_dk_live_…, issued in the portal. The server uses it to fetch its own licence daily
BUGB_LICENSE_TOKEN A pinned token, three base64url segments separated by dots. For deployments that cannot reach the portal. Setting it disables refresh
BUGB_LICENSE_PUBLIC_KEY Not read by a published image, which verifies against the key stamped into it. Harmless to leave set; the server logs one line saying it is ignoring it. It is still the key a development build reads
BUGB_PORTAL_URL Where the portal lives. Empty uses the URL compiled into the image (https://account.bugb.io). Set it only for a staging estate or an internal mirror
  1. Issue the key.

    Go to account.bugb.io/dashboard/deployment-keys and issue one. It is shown once, at issue. If it is lost, revoke it there and issue another.

    It is a bearer credential, so treat it like the database password. Its only power is to fetch the current licence for its own account.

    That page also shows, per key, when the deployment was last seen, the IP it came from, and the image version it reported. That is where to look first when a licence is not behaving.

  2. Put it where the server reads it.

    Under Compose, in deploy/.env:

    BUGB_DEPLOYMENT_KEY=bugb_dk_live_xxxxxxxx
    BUGB_LICENSE_TOKEN=

    Under Helm, in a Secret you create yourself:

    Terminal window
    kubectl -n bugb create secret generic bugb-license \
    --from-literal=BUGB_DEPLOYMENT_KEY='bugb_dk_live_xxxxxxxx'

    then name it with --set license.existingSecret=bugb-license.

  3. Restart, and read the licence back.

    Terminal window
    docker compose -f deploy/docker-compose.yml up -d
    curl -s $URL/v1/license -H "Authorization: Bearer $TOKEN"
    {
    "mode": "licensed",
    "product": "bugb",
    "schema_version": 1,
    "tier": "team",
    "state": "valid",
    "active": true,
    "expires_at": "2027-09-03T00:00:00+00:00",
    "seats": 25,
    "seats_used": 2,
    "features": [],
    "repo_limit": null
    }

    "state": "valid" and "active": true is the answer you want. expires_at moves forward on its own from here: the server fetches a fresh 30 day term every day for as long as the subscription is live.

    A brand-new deployment licenses itself within seconds of its first boot, and there is a short window before its first poll returns in which it answers 402. That clears on its own, without a restart.

There is nothing further to do. No renewal, no diary entry, no second visit to the portal. A cancellation takes effect on its own about 37 days later, which is the 30 day term plus the grace window.

Leave BUGB_DEPLOYMENT_KEY unset and set BUGB_LICENSE_TOKEN to the pinned licence you were issued.

With no deployment key configured the server builds no HTTP client and makes no outbound request of any kind. This is a supported deployment, not a degraded one. Those licences are issued on a longer term through your engagement, because a token that cannot refresh cannot be renewed silently either.

Moving a pinned deployment to refresh is one change: set BUGB_DEPLOYMENT_KEY and clear BUGB_LICENSE_TOKEN.

GET /v1/license is the diagnostic. Any valid bearer token can read it, and it is never licence-gated: the route that answers “why am I getting a 402?” must not answer with another one.

mode / state Writes What it means
licensed, valid accepted normal
licensed, grace accepted past expires_at, inside the grace window
licensed, expired 402 past the grace window. Reads keep working
licensed, state: null, active: false 402 a licence is configured, or a key is, and nothing verified. See below
dev, state: null accepted a source build with no public key set. Not reachable from a published image

Writes still work, which is the point: a renewal stuck in procurement is not an outage.

{
"mode": "licensed",
"product": "bugb",
"schema_version": 1,
"tier": "team",
"state": "grace",
"active": true,
"expires_at": "2026-09-06T19:23:31+00:00",
"seats": 25,
"seats_used": 2,
"features": [],
"repo_limit": null
}
POST /v1/events -> HTTP 200

The window is a quarter of the term, capped at 14 days. An annual licence gets the full fortnight; a 30 day one gets seven and a half.

{"detail":"license expired: acme's team plan expired at 2026-06-03T00:00:00+00:00 and the grace window ended at 2026-06-17T00:00:00+00:00"}
HTTP 402

Reads are unaffected at the same moment:

GET /v1/repos -> HTTP 200

An expiry degrades a deployment rather than stopping one. Everything already pushed stays readable; what stops is filing anything new.

Three different causes produce the same /v1/license body, and the 402 detail is what tells them apart.

{"mode":"licensed","product":null,"schema_version":null,"tier":null,"state":null,"active":false,"expires_at":null,"seats":null,"seats_used":null,"features":[],"repo_limit":null}

A deployment key that has fetched nothing:

{"detail":"license malformed: no license token configured; set BUGB_LICENSE_TOKEN"}
HTTP 402

A mangled token:

{"detail":"license malformed: expected 3 dot-separated segments, got 1"}
HTTP 402

Copy BUGB_LICENSE_TOKEN again, whole and on one line.

A licence Bugb did not sign:

{"detail":"license bad_signature: signature does not verify against this key"}
HTTP 402

A published image verifies against the key stamped into it, so this also names the case where a licence was signed under a key generation no released image carries yet.

Two answers look like licensing problems and are not.

{"detail":"role 'viewer' cannot push events; member or above is required"}
HTTP 403

A 403 is the token’s role, not the deployment’s licence. Mint the caller a member token.

{"detail":"missing credential"}
HTTP 401

A 401 is a missing or invalid bearer token. 402 is the only status that means the deployment’s licence.

What a customer receives, and what to take back

Section titled “What a customer receives, and what to take back”

Publishing the server image privately put a second credential on both halves of this, and the second half is the one people forget.

Handed over at onboarding Taken back at offboarding
a registry credential for ghcr.io/bugb-technologies/bugb-server revoke it. Until it is revoked, a former customer can still pull every image ever published, including new ones
a deployment key bugb_dk_live_…, or a pinned licence token revoke the key in the portal. Refresh stops returning tokens and writes stop about 37 days later; a pinned token runs out

The two revocations are independent and neither implies the other. A lapsed licence stops writes but does not stop pulls; a revoked pull credential stops new deployments but does nothing to a running one, which already has the image on disk. Offboarding is both.