Operating Kaisin

Installing

helm install kaisin oci://ghcr.io/bluepawlabs/charts/kaisin \
  --namespace kaisin --create-namespace \
  --set host=kaisin.example.com \
  --set registry.repositoryPrefix=your-org \
  --set-string encryptionKey="$(openssl rand -base64 32)"

Kaisin is not public software. The chart and the four images it references live in private packages on ghcr.io, so both the helm install above and the cluster's own image pulls need credentials — a token with read:packages on bluepawlabs:

helm registry login ghcr.io -u <user> --password <token>

kubectl -n kaisin create secret docker-registry ghcr \
  --docker-server=ghcr.io --docker-username=<user> --docker-password=<token>
# then --set image.pullSecrets[0].name=ghcr on the install above

Only the command line is installable by anyone: brew install bluepawlabs/kaisin/kaisin fetches from a public tap repository holding nothing but a formula and the archives.

Generated inline like that, the key is written into the cluster and shown to nobody — read it back with kubectl -n kaisin get secret kaisin-protection -o jsonpath='{.data.encryptionKey}' | base64 -d and keep it, or generate it into a file first. It encrypts every credential Kaisin stores, and losing it does not stop Kaisin starting: it makes those credentials unrecoverable.

From a clone, when the chart itself is what you are changing, deploy/helm/kaisin replaces the oci:// reference.

Cluster prerequisites

Component Required Without it
Kubernetes yes Nothing works
A default StorageClass yes Databases stay pending
Traefik yes Domains cannot be served
cert-manager no TLS certificates must be supplied by hand
metrics-server no Resource usage is not shown

Kaisin reports all of these on its Kubernetes screen. Missing optional components are stated as guidance, not errors.

The encryption key

encryptionKey encrypts every credential Kaisin stores. Back it up.

kubectl -n kaisin get secret kaisin-protection -o jsonpath='{.data.encryptionKey}' | base64 -d

If it is lost, stored registry passwords, Git tokens and database passwords cannot be decrypted. Kaisin will start, but every affected operation will fail until the credentials are re-entered.

First sign-in

A fresh installation has no administrator. Opening it shows a setup screen, which asks for the installation's setup token before it will create one:

kubectl -n kaisin get secret kaisin-setup -o jsonpath='{.data.token}' | base64 -d

Needing cluster access to read that token is what makes the setup screen safe to expose: without it, a new installation on a public hostname would be claimable by whoever reached it first — and Kaisin can deploy into the cluster, so claiming it means taking the cluster.

The token is held in a Secret rather than printed to the log, because a log line does not survive the pod being replaced. It stays valid until setup completes and is stable across upgrades.

Setting bootstrap.password skips all of this and creates the account directly. That is for development and unattended installs, where the value comes from configuration that is already trusted.

A lost administrator password

Only the hash is stored, so the password cannot be recovered. Remove the accounts instead and the installation returns to its setup screen, where the setup token lets you create a new owner:

kubectl -n kaisin exec -it kaisin-postgres-0 -- psql -U kaisin -d kaisin \
  -c 'delete from "Sessions"; delete from "Users";'

Nothing else is touched: projects, applications, deployments and stored credentials all belong to the workspace, not to the user.

Permissions

Kaisin's ClusterRole is enumerated in deploy/helm/kaisin/templates/rbac.yaml. It is broad — Kaisin manages workloads across the namespaces it creates — but it is never cluster-admin, and nodes, storage classes and metrics are read-only.

Two reads are narrower than the rest and worth knowing about, because both are the kind of permission somebody auditing this will stop at.

metrics.ingressTraffic is on, and grants a read-only proxy to pods in one namespace — by default kube-system, where k3s puts Traefik. That is what fills in the requests and error-rate figures on an application's overview. Traefik counts every request it routes and publishes the counts already; nothing is installed or reconfigured to make this work, and the reason a grant is needed at all is that the counts live on the ingress pod rather than on its Service. The cluster-wide equivalent, pods/proxy in every namespace, would let Kaisin make HTTP requests to anything in the cluster, which is why this is a Role and not a ClusterRole. Set metrics.ingressTraffic=false to withhold it; the traffic panel then says it has no numbers, which is true.

Set metrics.ingressNamespace if your ingress runs somewhere else — Traefik's own chart usually installs into traefik.

Seeing what the traffic is

The counters say how many requests arrived; they carry no path, so they cannot say what was asked for. That comes from Traefik's access log, which is off by default. With it on, the overview gains a panel listing the busiest paths and the clients asking for them — which is how a figure like "10,000 requests" turns out to be a few hundred real visits and a steady drip of scanners asking for /.env.

The panel offers to turn it on, which patches the ingress controller's Deployment and restarts it. On k3s that holds until the next chart sync puts Traefik's own configuration back, and the permanent form is a HelmChartConfig — which has to be written by hand, because it means merging into values you authored:

# k3s: /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    logs:
      access:
        enabled: true
        format: json

Nothing is shipped anywhere. Kaisin reads what the ingress container still holds, which is a window rather than a history — the panel says how far back it reached. No extra permission is needed: reading pod logs is already how the Logs tab works.

metrics.nodeStats is off, and is discussed under its own value in values.yaml.

Upgrading

helm upgrade kaisin oci://ghcr.io/bluepawlabs/charts/kaisin \
  --namespace kaisin --set host=admin.example.com ... --wait

No version asked for takes the newest release; --version 1.1.23 pins it. From a clone instead — which is what you want when the chart itself is what changed — the path deploy/helm/kaisin replaces the oci:// reference.

No image.tag. The chart carries the images it was released with, so choosing a chart is choosing a set of images that were built and tested together; a tag pinned by hand leaves the Helm release and the running code disagreeing, and every later upgrade inherits it.

Pass the values explicitly. --reuse-values looks convenient and quietly carries forward every value from the previous revision, including ones a later chart has since changed defaults for — an image tag pinned during a repair, or ingress.tls=false set while debugging, will survive every subsequent upgrade and produce a cluster that disagrees with the chart for reasons nothing in the release history explains. --reset-then-reuse-values is the safer form if you want most of them.

The API applies migrations on start; the controller and worker wait for the schema. Both the encryption key Secret and the control-plane PostgreSQL password Secret carry helm.sh/resource-policy: keep, so they survive upgrades and uninstalls.

Deleting things

Deletion is always explicit, and deleting a workload is never the same action as deleting data.

Action Removes Keeps
Delete application Deployment, Service, routes, ConfigMap, Secret Deployment history, attached databases and their data
Delete resource (keep data) StatefulSet, Service PersistentVolumeClaim, credential Secret
Delete resource (delete data) Everything, including the volume Nothing
Delete project All environments and their namespaces Refused while managed resources still exist

Diagnosing a failed deployment

  1. Application → Deployments shows the failure message on the deployment itself.
  2. Build logs (the icon on the deployment row) show what the build did, stage by stage.
  3. Application → Kubernetes lists the objects Kaisin created and recent cluster events, translated into plain language.
  4. Application → Logs streams runtime output from every instance.

Common causes:

Symptom Usual cause
Build fails immediately No registry configured, or push credentials are wrong
Deployment stuck in Deploying The health check path returns non-2xx, or the container exits
ImagePullBackOff The cluster cannot reach the registry, or needs an image pull secret
Domain stuck Pending DNS does not point at the cluster, or cert-manager has not issued yet
Database stuck Provisioning No StorageClass, or no capacity to bind the volume

Scaling the control plane

Backups

Kaisin's own state is entirely in its PostgreSQL database:

kubectl -n kaisin exec sts/kaisin-postgres -- pg_dump -U kaisin kaisin > kaisin-backup.sql

Databases Kaisin provisions for your applications are separate, and Kaisin does not back them up. That is deliberate: Kaisin does not read user application data.