kadet-hub logokadet-hub

Gotify on Kubernetes: a self-hosted push notification server

gotify 3.1.1, a server that takes messages from applications through a REST API and delivers them to its web UI and Android app, with its SQLite database on a claim, designed in kadet-hub on gotify’s image and rendered by the kadet-hub kubernetes generator 1.14.0.

What it deploys

Required parameters

applications.gotify.admin_password is GOTIFY_DEFAULTUSER_PASS, the password of the admin admin that gotify creates at a start while its database has no user (DEC-344 in the spec). Set it as a ref.

gotify reads the password only when it creates the admin, so a changed parameter does not change the password of an existing admin. Change it in the web UI.

Without the parameter the compile stops with Interpolation key and its name.

Include

A target includes the recipe and its options as classes and can override anything the recipe sets:

classes:
  - recipes.gotify
parameters:
  namespace: gotify
  kapitan:
    vars:
      target: gotify
  applications:
    gotify:
      admin_password: <ref>

Fetching it

The quickstart goes through these steps on a kind cluster with the uptime-kuma recipe, from installing Kapitan to the running app.

A separate fetch inventory pins the recipe by version and digest. This is fetch/targets/recipes.yml for version 1.0.0:

parameters:
  kapitan:
    dependencies:
      - type: oci
        source: ghcr.io/kadet-hub/recipes/gotify:1.0.0@sha256:4a43d46c4c3fac327a4d77e8636453d6ca2d6483133416f86c1d628503f0a785
        output_path: inventory/classes/recipes/gotify

The first command fetches the recipe into inventory/classes/recipes/gotify, the second the generators and compiles:

kapitan compile --inventory-path fetch --fetch --output-path .
kapitan compile --fetch

The first command exits with status 1 because the fetch target compiles nothing. Kapitan's --fetch checks neither the digest nor the attestation. This verifies the attestation of the recipe:

gh attestation verify \
  oci://ghcr.io/kadet-hub/recipes/gotify@sha256:4a43d46c4c3fac327a4d77e8636453d6ca2d6483133416f86c1d628503f0a785 \
  --owner kadet-hub \
  --cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main

The attested index must also list each digest under its recipe with the pinned version, and not as yanked. Verifying by hand shows how to read it.

Settings

Setting Inventory
gotify version components.gotify.image, tag and digest
the variables of gotify-server.env.example, such as GOTIFY_DEFAULTUSER_NAME or GOTIFY_SERVER_TRUSTEDPROXIES components.gotify.env.<variable>, credentials through components.gotify.secrets with a ref
size of the claim data, 1Gi components.gotify.volume_claims.data.spec.resources.requests.storage
exposure a route or Ingress of the target to the Service gotify on port 80
resources components.gotify.resources

The claim data is mounted at /app/data, the directory that the installation page mounts. It holds the SQLite database gotify.db, the uploaded application images in images and the directory plugins, from which gotify loads plugins at its start. SQLite allows one replica.

gotify listens on its default port 80. The pod sets the sysctl net.ipv4.ip_unprivileged_port_start to 0, so UID 1234 binds the port without a capability (DEC-343 in the spec). Pod Security baseline and restricted allow this sysctl.

The readiness probe gets /health, which the HEALTHCHECK of gotify’s Dockerfile asks. There is no liveness probe.

Proof

check gotify --level deploys with tests/target.yml, on kind for each Kubernetes minor CI tests. The image has no Kubernetes objects to compare with, so the recipe is proven by deploying it (RCP-44).

tests/smoke checks that the pod passes Pod Security restricted and starts a pod of gotify’s image, which carries curl. From there, through the cluster IP of the Service gotify on port 80, it

Versions

The recipe's files are in kadet-hub/recipes. All recipes