Skip to content

Deploy the server with Helm

The chart is deliberately small: a Deployment, a Service, a ServiceAccount, and a Secret. It is the path for teams who already have a cluster and a managed Postgres.

For a single host, see Deploy the server with Docker Compose.

  • A registry credential for ghcr.io/bugb-technologies/bugb-server. The image is published to a private repository, and the chart takes the credential as a docker-registry Secret through imagePullSecrets.
  • A deployment key (bugb_dk_live_…) or a pinned licence token. A published server holds a licence or answers 402 to everything.
  • The chart, which ships in the deployment bundle at deploy/helm/.
  • A Postgres your platform team runs and backs up. RDS, Cloud SQL, or an operator-managed cluster. This is the supported arrangement.
  • Kubernetes 1.23 or newer, and Helm 3.
  1. Give the cluster a way to pull the image.

    image.repository already points at the private package, so the credential is the only thing missing:

    Terminal window
    kubectl create namespace bugb
    kubectl -n bugb create secret docker-registry bugb-ghcr \
    --docker-server=ghcr.io \
    --docker-username="$BUGB_REGISTRY_USER" \
    --docker-password="$BUGB_REGISTRY_TOKEN"

    If the cluster cannot reach ghcr.io, mirror the image into a registry it can and override image.repository. Set image.digest rather than a tag if you want a reference that cannot be repointed at all; the publish workflow prints it.

  2. Put the DSN and the licence in Secrets you create yourself.

    Terminal window
    kubectl -n bugb create secret generic bugb-db \
    --from-literal=BUGB_SERVER_DSN='postgresql://bugb:PASSWORD@postgres.db.svc.cluster.local:5432/bugb'
    kubectl -n bugb create secret generic bugb-license \
    --from-literal=BUGB_DEPLOYMENT_KEY='bugb_dk_live_xxxxxxxx'

    Create the licence Secret before the install, not after. A published server with no licence serves nothing, so a deployment installed without one comes up refusing every request.

    Use database.existingSecret rather than --set database.dsn=.... A DSN passed on the command line lands in your shell history and in the release’s stored values, where helm get values hands it back to anyone with list rights. database.dsn exists for labs.

  3. Install.

    Terminal window
    helm -n bugb install bugb ./deploy/helm \
    --set imagePullSecrets[0].name=bugb-ghcr \
    --set database.existingSecret=bugb-db \
    --set license.existingSecret=bugb-license

    That renders a Deployment named bugb-bugb-server, from the release name and the chart name, running ghcr.io/bugb-technologies/bugb-server:0.1.0. image.tag defaults to the chart’s appVersion, so it is needed only when you want a different one.

  4. Watch it come up.

    Terminal window
    kubectl -n bugb rollout status deploy/bugb-bugb-server

    The app applies its own schema on startup, so there is no separate migration job to run.

  5. Check it reaches its database.

    Terminal window
    kubectl -n bugb port-forward svc/bugb-bugb-server 8000:8000
    curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/v1/repos
    401

    401 is the good answer: the request reached the auth check, which opens a database connection before refusing you. A 500 here means the database is unreachable, and a 402 means the deployment holds no licence it accepts.

The chart reads BUGB_DEPLOYMENT_KEY, BUGB_LICENSE_TOKEN and BUGB_LICENSE_PUBLIC_KEY from the Secret named by license.existingSecret. Every one of them is mounted optional: true, so a Secret carrying only one, or a deployment part-way through a licence rollout, does not wedge the pod in CreateContainerConfigError.

Set the deployment key or the pinned token, never both: a deployment carrying both runs on the pinned value and ignores every token it fetches. BUGB_LICENSE_PUBLIC_KEY is not read by a published image, which verifies against the key stamped into it, so it is harmless to leave in the Secret and the server logs one line saying it is ignoring it.

The inline equivalents are license.deploymentKey and license.token, which are fine for a lab and put the credential in Helm’s release history. See License a deployment for which values a deployment should carry, and for the one combination that fails silently.

Terminal window
kubectl -n bugb exec deploy/bugb-bugb-server -- \
python scripts/mint_token.py --tenant acme --identity alice --role member
minted member token for alice@acme — shown once, not stored
bugb_ZRbsvgLqZdCncXcvGxvUOIEG3-KHjZMcy9i3YjdfUtU

Minting is a write, so it is licence-gated: on a deployment holding no licence the script refuses rather than handing out a credential nothing will accept.

The first admin token is minted this way; after that a tenant’s own admin manages their people over HTTP. There is no endpoint that mints across tenants on purpose, because minting a token for any tenant is the ability to write into any tenant, so it stays a DSN-in-hand operation.

One replica, and a Recreate strategy. This is a correctness constraint, not a default to tune. Event ingest assigns a sequence number by reading the current maximum and continuing from it, serialised only by an in-process lock. Two pods, including the two that briefly overlap during a rolling update, can interleave sequence numbers within a run. A reordered log is a reordered fold, and that is a wrong verdict arrived at silently.

Recreate trades a few seconds of downtime for that. The chart warns if you raise replicaCount.

No Ingress template. Every cluster wants a different object, and pointing your own at the Service is a smaller job than fighting a template that guessed wrong. Bearer tokens travel over this Service, so do not expose it without TLS in front.

No Postgres subchart is declared. The supported database is one your platform team already backs up. A Postgres that helm uninstall can take with it is the wrong home for every finding your team has ever pushed. Chart.yaml carries a commented dependencies: block and instructions if you want one anyway for a proof of concept.

Hardened by default: non-root uid 10001, readOnlyRootFilesystem, all capabilities dropped, and no service account token mounted, because the server calls no Kubernetes API.

Terminal window
helm -n bugb upgrade bugb ./deploy/helm --set image.tag=0.2.0

The pod is replaced and the new one applies the schema on startup. helm rollback returns the image; it does not return the database, and migrations are forward only. Take a database backup before an upgrade that changes the schema, because that backup is the only real rollback.