Kubernetes Guide¶
maintenant runs natively on Kubernetes with read-only RBAC, namespace filtering, and workload-level monitoring out of the box.
Deployment¶
Helm (recommended)¶
This is the recommended approach for production clusters. See the Helm section below for full options.
On a managed cluster, name the storage class your provider's CSI driver installs, or the PVC stays Pending:
| Cluster | Value |
|---|---|
Hetzner (kube-hetzner, hetzner-k3s, Talos) |
--set persistence.storageClass=hcloud-volumes, see Kubernetes on Hetzner |
| DigitalOcean (DOKS) | --set persistence.storageClass=do-block-storage-retain, see Kubernetes on DOKS |
| Scaleway (Kapsule) | --set persistence.storageClass=sbs-default, see Kubernetes on Kapsule |
| OVHcloud (MKS) | --set persistence.storageClass=csi-cinder-high-speed, see Kubernetes on Managed Kubernetes |
| Vultr (VKE) | --set persistence.storageClass=vultr-block-storage-retain, see Kubernetes on VKE |
Raw manifests¶
Apply the provided manifests:
This creates:
| Resource | Description |
|---|---|
| ServiceAccount | maintenant — identity for API access |
| ClusterRole | Read-only access to pods, logs, services, events, workloads, and metrics |
| ClusterRoleBinding | Binds the role to the service account |
| Deployment | Single replica with security hardening |
| PersistentVolumeClaim | 10 Gi for SQLite storage — migrations rebuild tables in place and need several times the database size |
| Service | ClusterIP on port 80 |
RBAC Permissions¶
maintenant requests the minimum permissions needed for monitoring:
rules:
# Core resources — read-only
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "namespaces", "events"]
verbs: ["get", "list", "watch"]
# Workloads — read-only
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["get", "list", "watch"]
# Metrics — read-only
- apiGroups: ["metrics.k8s.io"]
resources: ["pods"]
verbs: ["get", "list"]
maintenant never creates, modifies, or deletes any resource in your cluster.
Metrics Server required
Resource metrics (CPU/memory) require metrics-server to be installed in the cluster. Container monitoring works without it.
Security Hardening¶
The default deployment includes:
securityContext:
runAsNonRoot: true
runAsUser: 65534 # nobody
runAsGroup: 65534
fsGroup: 65534
containers:
- securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
The container runs as nobody (uid 65534) with an immutable root filesystem and all Linux capabilities dropped. A /tmp emptyDir is mounted for SQLite WAL temporary files since the root filesystem is read-only.
This mirrors the Docker Compose hardening (read_only: true, no-new-privileges, non-root user). See Security for the full container security reference.
Namespace Filtering¶
By default, maintenant monitors all namespaces. Use environment variables to restrict scope:
Allowlist¶
Only monitor specific namespaces:
Blocklist¶
Monitor all namespaces except specific ones:
System namespaces
kube-system and kube-public are excluded by default when using the blocklist.
You do not need to add them explicitly.
If both MAINTENANT_K8S_NAMESPACES and MAINTENANT_K8S_EXCLUDE_NAMESPACES are set, the allowlist takes precedence.
Workload Monitoring¶
maintenant groups pods by their owning workload:
| Workload | What maintenant tracks |
|---|---|
| Deployment | Replica count, ready pods, rollout status |
| StatefulSet | Ordered pod states, persistent volume claims |
| DaemonSet | Node coverage, desired vs ready counts |
Each workload appears as a single entry in the dashboard with aggregated health status. Individual pods are accessible in the detail view.
Runtime Detection¶
maintenant auto-detects Kubernetes in this order:
MAINTENANT_RUNTIME=kubernetesenvironment variable (explicit override)KUBERNETES_SERVICE_HOSTenvironment variable (set automatically by Kubernetes for in-cluster pods)KUBECONFIGenvironment variable or~/.kube/configfile (for out-of-cluster development)
To force Kubernetes mode:
Health Probes¶
The deployment includes startup, liveness and readiness probes:
startupProbe:
httpGet:
path: /api/v1/health
port: http
periodSeconds: 10
failureThreshold: 60
livenessProbe:
httpGet:
path: /api/v1/health
port: http
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/v1/health
port: http
initialDelaySeconds: 3
periodSeconds: 10
Do not drop the startup probe
Schema migrations run before the HTTP listener starts, so /api/v1/health
stays silent for their whole duration — minutes on a large database, and longer
for the one-time UUID conversion. Without a startup probe the liveness probe
kills the pod about 95 s in, leaving the migration half-applied and the schema
marked dirty, and the same thing happens on every restart. The
failureThreshold: 60 above grants 10 minutes; raise it if an upgrade needs
more.
Do not make the probe fail on a database outage
With an external PostgreSQL, /api/v1/health deliberately answers 200
even when the database is unreachable — the outage is reported in
storage.connected. A probe that failed on it would restart the pod exactly
when the database needs to be left alone, and restarting fixes nothing: the
instance recovers on its own once the database answers. If you want to
alert on it, read storage.connected; do not wire it to a probe. See
PostgreSQL storage.
Resource Limits¶
Default resource requests and limits:
Adjust based on the number of monitored workloads. maintenant is lightweight — 50-100 workloads run comfortably within these limits.
Scaling Considerations¶
Single replica only
maintenant uses SQLite with a single-writer pattern. The deployment strategy is set to
Recreate — do not scale beyond 1 replica.
For high availability, ensure your PersistentVolumeClaim uses a storage class with adequate durability.
Exposing the Dashboard¶
Ingress¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: maintenant
namespace: maintenant
spec:
rules:
- host: now.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: maintenant
port:
name: http
Port Forward (Development)¶
Open http://localhost:8080.
Helm¶
The chart is located in deploy/helm/maintenant/.
Minimal install¶
With Ingress¶
helm install maintenant ./deploy/helm/maintenant \
-n maintenant --create-namespace \
--set ingress.enabled=true \
--set ingress.host=maintenant.example.com \
--set ingress.className=nginx
With TLS¶
# values-prod.yaml
ingress:
enabled: true
className: nginx
host: maintenant.example.com
tls:
- secretName: maintenant-tls
hosts:
- maintenant.example.com
helm install maintenant ./deploy/helm/maintenant \
-n maintenant --create-namespace \
-f values-prod.yaml
Enterprise license¶
Pass the license key directly:
Or reference an existing secret (recommended for GitOps):
kubectl create secret generic maintenant-license \
--from-literal=license-key=YOUR_LICENSE_KEY \
-n maintenant
helm install maintenant ./deploy/helm/maintenant \
--set license.existingSecret=maintenant-license
Key values¶
| Value | Default | Description |
|---|---|---|
image.tag |
"" (chart appVersion) |
Image tag to deploy |
runtime |
kubernetes |
kubernetes or docker |
persistence.size |
1Gi |
SQLite volume size |
persistence.storageClass |
"" |
Storage class (cluster default if empty) |
persistence.existingClaim |
"" |
Use an existing PVC |
ingress.enabled |
false |
Enable Ingress resource |
ingress.host |
maintenant.example.com |
Ingress hostname |
ingress.className |
"" |
Ingress class |
license.key |
"" |
Enterprise license key |
license.existingSecret |
"" |
Existing secret name for the license key |
resources |
see values.yaml | CPU/memory requests and limits |
Upgrade¶
Uninstall¶
PVC not deleted on uninstall
Helm does not delete PersistentVolumeClaims on uninstall to prevent accidental data loss.
Delete it manually if needed: kubectl delete pvc maintenant-data -n maintenant
Related¶
- Installation — Docker and source builds
- Hetzner Cloud Deployment — Clusters built with kube-hetzner, hetzner-k3s or Talos
- DigitalOcean Deployment — DOKS storage classes and Load Balancer checks
- Scaleway Deployment — Kapsule storage classes and Private Network DNS
- OVHcloud Deployment — MKS storage classes and zone-pinned volumes
- Vultr Deployment — VKE storage classes and the 10Gi PVC floor
- Configuration — Environment variables
- Container Monitoring — How workloads are tracked
- Resource Metrics — CPU/memory from metrics-server