kadet-hub logokadet-hub

Uptime Kuma on Kubernetes without Docker Compose, with Kapitan

Uptime Kuma, a self-hosted monitoring tool with status pages, with its data in SQLite on a claim, rendered by the kadet-hub kubernetes generator 1.14.0 from the Docker Compose file of Uptime Kuma 2.5.9.

What it deploys

Required parameters

No parameter has to be set.

Uptime Kuma creates its admin account in the web UI on the first visit, without a login (DEC-66 in docs/specs/recipes.md). Until then anyone who reaches the Service can claim the instance, so create the account before the target exposes it.

Include

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

classes:
  - recipes.uptime-kuma
parameters:
  namespace: uptime-kuma
  kapitan:
    vars:
      target: uptime-kuma

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.1:

parameters:
  kapitan:
    dependencies:
      - type: oci
        source: ghcr.io/kadet-hub/recipes/uptime-kuma:1.0.1@sha256:dfa63d2f7c6eb147ca0ea28d8f5d5f0f1bb2a5cba4fac9ba5bbe3fbc8d7a51d8
        output_path: inventory/classes/recipes/uptime-kuma

The first command fetches the recipe into inventory/classes/recipes/uptime-kuma, 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/uptime-kuma@sha256:dfa63d2f7c6eb147ca0ea28d8f5d5f0f1bb2a5cba4fac9ba5bbe3fbc8d7a51d8 \
  --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
Uptime Kuma version components.uptime-kuma.image, tag and digest
database type, sqlite components.uptime-kuma.env.UPTIME_KUMA_DB_TYPE
other environment variables of Uptime Kuma, such as UPTIME_KUMA_WS_ORIGIN_CHECK components.uptime-kuma.env.<variable>
exposure components.uptime-kuma.service.type, or a route or Ingress of the target to the Service uptime-kuma on port 3001
size of the claim data, 1Gi components.uptime-kuma.volume_claims.data.spec.resources.requests.storage
resources components.uptime-kuma.resources

Uptime Kuma keeps its state in /app/data on the claim: the SQLite database kuma.db, db-config.json, which it writes from UPTIME_KUMA_DB_TYPE at every start, and uploaded files. SQLite allows one replica.

The Service uptime-kuma serves plain HTTP on port 3001, the port the compose file publishes. The web UI talks to the server over socket.io, which upgrades to a WebSocket on the same port. A route or Ingress in front of it must pass the WebSocket upgrade and keep the Host header, because Uptime Kuma refuses a WebSocket whose Origin names another host.

A logged-in user’s monitors reach whatever the pod reaches, a cloud’s metadata endpoint among them. Upstream leaves that to the platform (GHSA-qjxc-h5jf-c7rj), so restrict the pod’s egress where it matters.

Mapping

The service uptime-kuma becomes the component uptime-kuma. Its name stays, so no reference is rewritten.

Compose Recipe
service uptime-kuma StatefulSet uptime-kuma, Service uptime-kuma
louislam/uptime-kuma:2 docker.io/louislam/uptime-kuma:2.5.9@sha256:e88415a90dd2f7efecdbc18de0490aff292764a3ac471531bf1d0481725591bd, which 2 was at migration
port 3001:3001 Service port http on 3001 to the container’s port 3001, ClusterIP
bind mount ./data at /app/data claim template data, ReadWriteOnce, 1Gi
the image’s HEALTHCHECK, extra/healthcheck readiness probe sh -c extra/healthcheck every 60 seconds with a timeout of 30 seconds and 5 failures
restart: unless-stopped not rendered
not in the compose file UPTIME_KUMA_DB_TYPE: sqlite (DEC-65)

Proof

check uptime-kuma --level deploys with tests/target.yml, on kind for each Kubernetes minor CI tests, with check 0.14.0. A compose file has no Kubernetes objects to compare with, so the recipe is proven by deploying it (RCP-38).

tests/smoke starts a pod of Uptime Kuma’s image, which carries curl, node and the socket.io client. From there, through the Service uptime-kuma on port 3001, it

Not tested: the web UI in a browser, notifications, status pages, and a route or Ingress in front of the Service.

Versions

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