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.
- Latest version:
1.0.1, published - Source: Docker Compose file
louislam/uptime-kuma@df601c58d9205562f34cfe5ef35264b341019d19(compose.yaml),sha256:f62a065cabc4671225ec4e58575dd9cca364b0d0b5087b57451278791672a54b - Proof level:
deploys, deployed on kind and smoke tested by the kadet-hub/recipes CI renderspassed in the release run of with check 0.18.0, kind v0.33.0- Output capabilities: none
- Inventory backends:
omegaconf - Generators: kubernetes 1.14.0
sha256:787129e1eaed6b9bc868fe4c4aec58d40de6a89fc3cc08424a4cb5822b855080 - Image security: grade F 2440 findings, one per advisory and package, in the 1 image of version 1.0.1: CRITICAL 18, HIGH 1191, MEDIUM 696, LOW 454, UNKNOWN 81. Scanned 2026-11-07 11:47 UTC. Full report
The grade rates the upstream images the recipe pins, not the recipe:docker.io/louislam/uptime-kuma.
What it deploys
recipes.uptime-kuma(init.yml)recipes.uptime-kuma.uptime-kuma(uptime-kuma.yml):uptime-kuma
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
- requires
entryPagefrom/api/entry-page, so the database setup page did not start, and the web UI from/ - gets 401 from
/metricswithout a login - creates the admin account with the socket.io event
setup, logs in, adds an HTTP monitor against CoreDNS’ metrics onkube-dns.kube-system:9153and waits for a heartbeat UP - requires the monitor UP from
/metricswith the admin account - deletes the pod
uptime-kuma-0and, once the new pod is ready, logs in again, finds the monitor and waits for a new heartbeat UP, which needskuma.dbon the claim.
Not tested: the web UI in a browser, notifications, status pages, and a route or Ingress in front of the Service.
Versions
1.0.1, :sha256:dfa63d2f7c6eb147ca0ea28d8f5d5f0f1bb2a5cba4fac9ba5bbe3fbc8d7a51d81.0.0, :sha256:48a231c3fda46a2a34d5e25e352a7f449f6efbb783cef6662214e6bdb603f2db
The recipe's files are in kadet-hub/recipes. All recipes