Compare commits

..

4 Commits

Author SHA1 Message Date
7438b18313 done tick version bump, fixed bug with adguard 2026-09-15 12:14:12 +02:00
5497301740 removed checkmk 2026-09-15 11:45:13 +02:00
d84a669a62 adguard home release 0.0.1 2026-09-15 11:37:19 +02:00
84168077ee norish upgrade 2026-08-20 15:32:03 +02:00
22 changed files with 1002 additions and 533 deletions

View File

@ -0,0 +1,19 @@
apiVersion: v2
name: adguard-home
description: AdGuard Home helm chart for Kubernetes - Network-wide ad and tracker blocking DNS server
type: application
version: 0.0.2
appVersion: "v0.107.79"
maintainers:
- name: Richard Tomik
email: no@m.com
keywords:
- dns
- ad-blocking
- dns-filtering
- adguard-home
- privacy
- network-security
home: https://github.com/rtomik/helm-charts
sources:
- https://github.com/AdguardTeam/AdGuardHome

View File

@ -0,0 +1,55 @@
1. Get the admin dashboard URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[?(@.name=='web')].nodePort}" services {{ include "adguard-home.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "adguard-home.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "adguard-home.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.ports.web.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "adguard-home.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use the AdGuard Home setup wizard / dashboard"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:{{ .Values.ports.web.port }}
{{- end }}
2. On first access, complete the AdGuard Home setup wizard (admin user, DNS listener
interfaces/ports). The resulting AdGuardHome.yaml is written to the conf volume.
3. DNS listens on port {{ .Values.ports.dns.port }} (TCP/UDP).
{{- if not .Values.hostNetwork }}
NOTE: hostNetwork is disabled, so clients will see the Service/pod IP, not their
real IP, in the AdGuard Home query log/filters. Set hostNetwork: true, or use a
LoadBalancer Service with externalTrafficPolicy: Local, to preserve client IPs.
{{- end }}
{{- if .Values.ports.dhcp.enabled }}
4. DHCP server is enabled on UDP 67/68.
{{- if not .Values.hostNetwork }}
WARNING: DHCP requires hostNetwork: true to function correctly - it will not
work through a ClusterIP/LoadBalancer Service.
{{- end }}
{{- end }}
{{- if or .Values.persistence.work.enabled .Values.persistence.conf.enabled }}
5. Data is persisted using PVCs:
{{- if .Values.persistence.work.enabled }}
- {{ .Values.persistence.work.existingClaim | default (printf "%s-work" (include "adguard-home.fullname" .)) }} (query log, filter cache, stats)
{{- end }}
{{- if .Values.persistence.conf.enabled }}
- {{ .Values.persistence.conf.existingClaim | default (printf "%s-conf" (include "adguard-home.fullname" .)) }} (AdGuardHome.yaml)
{{- end }}
{{- else }}
5. WARNING: No persistence enabled. Configuration and filter data will be lost when
the pod restarts.
{{- end }}
For more information about using this Helm chart, please refer to the readme.md file.

View File

@ -0,0 +1,235 @@
# AdGuard Home Helm Chart
A Helm chart for deploying [AdGuard Home](https://github.com/AdguardTeam/AdGuardHome), a network-wide DNS ad and tracker blocker, on Kubernetes.
## Introduction
This chart deploys AdGuard Home on a Kubernetes cluster using the Helm package manager. AdGuard Home is a self-hosted DNS server that blocks ads and trackers for every device on your network, and can also act as a DHCP server, DNS-over-TLS/HTTPS/QUIC resolver, and DNSCrypt server.
Source code: https://github.com/rtomik/helm-charts/tree/main/charts/adguard-home
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- PV provisioner support (if persistence is needed)
- A way to route real LAN clients to the DNS Service (LoadBalancer/MetalLB, NodePort, or `hostNetwork: true`) if you intend to use it as your network's resolver
## Installing the Chart
```bash
helm repo add rtomik https://rtomik.github.io/helm-charts
helm install adguard-home rtomik/adguard-home
```
## Uninstalling the Chart
```bash
helm uninstall adguard-home
```
## Configuration Examples
### Minimal Installation (admin UI only, no DNS exposed on the LAN)
```yaml
persistence:
work:
enabled: true
size: 2Gi
conf:
enabled: true
size: 100Mi
ingress:
enabled: true
hosts:
- host: adguard.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- adguard.example.com
secretName: adguard-tls
```
### Expose DNS to the LAN via LoadBalancer (e.g. MetalLB)
```yaml
service:
type: LoadBalancer
loadBalancerIP: 192.168.1.53
annotations:
metallb.universe.tf/allow-shared-ip: adguard-home
hostNetwork: true # preserves real client IPs in the query log/filters
```
### Enable DNS-over-TLS / DNS-over-QUIC and DNS-over-HTTPS
```yaml
ports:
dot:
enabled: true
https:
enabled: true
```
### Enable the DHCP server
DHCP requires `hostNetwork: true` - it cannot be proxied through a ClusterIP/LoadBalancer Service.
```yaml
hostNetwork: true
ports:
dhcp:
enabled: true
```
### Use an existing PVC
```yaml
persistence:
work:
existingClaim: "adguard-work-pvc"
conf:
existingClaim: "adguard-conf-pvc"
```
## Parameters
### Global Parameters
| Name | Description | Default |
|------|-------------|---------|
| `nameOverride` | Override the release name | `""` |
| `fullnameOverride` | Fully override the release name | `""` |
### Image Parameters
| Name | Description | Default |
|------|-------------|---------|
| `image.repository` | AdGuard Home image repository | `adguard/adguardhome` |
| `image.tag` | Image tag | `v0.107.79` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets | `[]` |
### Deployment Parameters
| Name | Description | Default |
|------|-------------|---------|
| `replicaCount` | Number of replicas (keep at 1 - AdGuard Home is not multi-writer safe) | `1` |
| `revisionHistoryLimit` | Revisions to retain | `3` |
| `podSecurityContext.runAsNonRoot` | Run as non-root | `true` |
| `podSecurityContext.runAsUser` | User ID | `1000` |
| `podSecurityContext.runAsGroup` | Group ID | `1000` |
| `podSecurityContext.fsGroup` | Filesystem group ID | `1000` |
| `containerSecurityContext.capabilities.add` | Capabilities added (NET_BIND_SERVICE for ports < 1024) | `["NET_BIND_SERVICE"]` |
| `hostNetwork` | Use host networking (required for DHCP, recommended for accurate client IPs) | `false` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
### Ports Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ports.web.port` | Admin dashboard / setup wizard | `3000` |
| `ports.dns.port` | Plain DNS (TCP+UDP) | `53` |
| `ports.dot.enabled` | Enable DNS-over-TLS / DNS-over-QUIC | `false` |
| `ports.dot.port` | DoT/DoQ port (TCP+UDP) | `853` |
| `ports.https.enabled` | Enable DNS-over-HTTPS / HTTPS admin dashboard | `false` |
| `ports.https.port` | HTTPS port (TCP+UDP) | `443` |
| `ports.dnscrypt.enabled` | Enable DNSCrypt | `false` |
| `ports.dnscrypt.port` | DNSCrypt port (TCP+UDP) | `5443` |
| `ports.dhcp.enabled` | Enable DHCP server (67/68 UDP, requires `hostNetwork: true`) | `false` |
| `ports.pprof.enabled` | Enable the debug pprof API | `false` |
| `ports.pprof.port` | pprof port | `6060` |
### Service Parameters
| Name | Description | Default |
|------|-------------|---------|
| `service.type` | Service type | `ClusterIP` |
| `service.annotations` | Service annotations | `{}` |
| `service.loadBalancerIP` | Static LoadBalancer IP (e.g. for MetalLB) | `""` |
### Ingress Parameters
| Name | Description | Default |
|------|-------------|---------|
| `ingress.enabled` | Enable ingress (routes to the admin dashboard only) | `false` |
| `ingress.className` | Ingress class name | `""` |
| `ingress.annotations` | Ingress annotations | See values.yaml |
| `ingress.hosts` | Ingress hosts | See values.yaml |
| `ingress.tls` | TLS configuration | See values.yaml |
### Persistence Parameters
| Name | Description | Default |
|------|-------------|---------|
| `persistence.work.enabled` | Persist `/opt/adguardhome/work` (query log, filter cache, stats) | `true` |
| `persistence.work.existingClaim` | Use an existing PVC instead of creating one | `""` |
| `persistence.work.storageClass` | Storage class | `""` |
| `persistence.work.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.work.size` | PVC size | `1Gi` |
| `persistence.conf.enabled` | Persist `/opt/adguardhome/conf` (AdGuardHome.yaml) | `true` |
| `persistence.conf.existingClaim` | Use an existing PVC instead of creating one | `""` |
| `persistence.conf.storageClass` | Storage class | `""` |
| `persistence.conf.accessMode` | Access mode | `ReadWriteOnce` |
| `persistence.conf.size` | PVC size | `100Mi` |
### Resource Parameters
| Name | Description | Default |
|------|-------------|---------|
| `resources` | Resource limits and requests | `{}` |
### Health Check Parameters
| Name | Description | Default |
|------|-------------|---------|
| `probes.liveness.enabled` | Enable liveness probe (TCP check on the admin web port) | `true` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `15` |
| `probes.liveness.periodSeconds` | Liveness period | `30` |
| `probes.readiness.enabled` | Enable readiness probe (TCP check on the admin web port) | `true` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `5` |
| `probes.readiness.periodSeconds` | Readiness period | `10` |
### Other Parameters
| Name | Description | Default |
|------|-------------|---------|
| `extraEnv` | Additional environment variables | `[]` |
| `extraVolumeMounts` | Additional volume mounts | `[]` |
| `extraVolumes` | Additional volumes | `[]` |
## Notes
- AdGuard Home has no supported way to bootstrap its admin account or DNS settings via
environment variables - complete the setup wizard once at `http://<web-service>:3000`
after the first install. The resulting `AdGuardHome.yaml` is written to the `conf` PVC,
so it survives pod restarts/upgrades.
- Because AdGuard Home stores state on disk and isn't multi-writer safe, do not scale
`replicaCount` beyond `1`.
- If you plan to use this as your network's DNS resolver, prefer `hostNetwork: true` (or
a LoadBalancer Service with `externalTrafficPolicy: Local`) so AdGuard Home sees real
client IPs rather than a single Service/NAT IP for every device.
## Troubleshooting
- **Clients all show up as the same IP / filters by client don't work**: enable `hostNetwork` or use `externalTrafficPolicy: Local` on a LoadBalancer Service.
- **DHCP doesn't hand out leases**: DHCP only works with `hostNetwork: true`.
- **Setup wizard settings don't persist**: verify `persistence.conf.enabled` is `true` and the PVC is bound.
```bash
kubectl logs -f deployment/adguard-home
kubectl describe pod -l app.kubernetes.io/name=adguard-home
```
## Links
- [AdGuard Home GitHub](https://github.com/AdguardTeam/AdGuardHome)
- [AdGuard Home Docker documentation](https://adguard-dns.io/kb/adguard-home/docker/)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/adguard-home)

View File

@ -1,14 +1,14 @@
{{/* {{/*
Expand the name of the chart. Expand the name of the chart.
*/}} */}}
{{- define "checkmk.name" -}} {{- define "adguard-home.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }} {{- end }}
{{/* {{/*
Create a default fully qualified app name. Create a default fully qualified app name.
*/}} */}}
{{- define "checkmk.fullname" -}} {{- define "adguard-home.fullname" -}}
{{- if .Values.fullnameOverride }} {{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }} {{- else }}
@ -20,16 +20,16 @@ Create a default fully qualified app name.
{{/* {{/*
Create chart name and version as used by the chart label. Create chart name and version as used by the chart label.
*/}} */}}
{{- define "checkmk.chart" -}} {{- define "adguard-home.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }} {{- end }}
{{/* {{/*
Common labels Common labels
*/}} */}}
{{- define "checkmk.labels" -}} {{- define "adguard-home.labels" -}}
helm.sh/chart: {{ include "checkmk.chart" . }} helm.sh/chart: {{ include "adguard-home.chart" . }}
{{ include "checkmk.selectorLabels" . }} {{ include "adguard-home.selectorLabels" . }}
{{- if .Chart.AppVersion }} {{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }} {{- end }}
@ -39,28 +39,7 @@ app.kubernetes.io/managed-by: {{ .Release.Service }}
{{/* {{/*
Selector labels Selector labels
*/}} */}}
{{- define "checkmk.selectorLabels" -}} {{- define "adguard-home.selectorLabels" -}}
app.kubernetes.io/name: {{ include "checkmk.name" . }} app.kubernetes.io/name: {{ include "adguard-home.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }} {{- end }}
{{/*
Name of the secret holding CMK_PASSWORD
*/}}
{{- define "checkmk.secretName" -}}
{{- .Values.config.adminPassword.existingSecret | default (printf "%s-secrets" (include "checkmk.fullname" .)) }}
{{- end }}
{{/*
Web UI path for health probes: /<siteId>/check_mk/login.py
*/}}
{{- define "checkmk.probePath" -}}
{{- printf "/%s/check_mk/login.py" .Values.config.siteId }}
{{- end }}
{{/*
tmpfs mount path derived from site ID
*/}}
{{- define "checkmk.tmpPath" -}}
{{- printf "/opt/omd/sites/%s/tmp" .Values.config.siteId }}
{{- end }}

View File

@ -0,0 +1,175 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "adguard-home.fullname" . }}
labels:
{{- include "adguard-home.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
selector:
matchLabels:
{{- include "adguard-home.selectorLabels" . | nindent 6 }}
strategy:
type: Recreate
template:
metadata:
labels:
{{- include "adguard-home.selectorLabels" . | nindent 8 }}
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.hostNetwork }}
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- if .Values.persistence.conf.enabled }}
initContainers:
- name: init-config
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
# AdGuard Home's first-run setup wizard defaults the admin web interface
# port to 80, regardless of ports.web.port below - if a user accepts that
# default, the Service/Ingress (which forward to ports.web.port) end up
# pointing at the wrong container port and the UI becomes unreachable
# (502/Bad Gateway) as soon as setup completes. Seeding http.address here
# before the wizard ever runs makes it pre-fill the correct port instead.
command:
- sh
- -c
- |
if [ ! -f /opt/adguardhome/conf/AdGuardHome.yaml ]; then
cat > /opt/adguardhome/conf/AdGuardHome.yaml <<'CONF'
http:
address: 0.0.0.0:{{ .Values.ports.web.port }}
CONF
fi
volumeMounts:
- name: conf
mountPath: /opt/adguardhome/conf
{{- end }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: web
containerPort: {{ .Values.ports.web.port }}
protocol: TCP
- name: dns-tcp
containerPort: {{ .Values.ports.dns.port }}
protocol: TCP
- name: dns-udp
containerPort: {{ .Values.ports.dns.port }}
protocol: UDP
{{- if .Values.ports.dot.enabled }}
- name: dot-tcp
containerPort: {{ .Values.ports.dot.port }}
protocol: TCP
- name: dot-udp
containerPort: {{ .Values.ports.dot.port }}
protocol: UDP
{{- end }}
{{- if .Values.ports.https.enabled }}
- name: https-tcp
containerPort: {{ .Values.ports.https.port }}
protocol: TCP
- name: https-udp
containerPort: {{ .Values.ports.https.port }}
protocol: UDP
{{- end }}
{{- if .Values.ports.dnscrypt.enabled }}
- name: dnscrypt-tcp
containerPort: {{ .Values.ports.dnscrypt.port }}
protocol: TCP
- name: dnscrypt-udp
containerPort: {{ .Values.ports.dnscrypt.port }}
protocol: UDP
{{- end }}
{{- if .Values.ports.dhcp.enabled }}
- name: dhcp-server
containerPort: 67
protocol: UDP
- name: dhcp-client
containerPort: 68
protocol: UDP
{{- end }}
{{- if .Values.ports.pprof.enabled }}
- name: pprof
containerPort: {{ .Values.ports.pprof.port }}
protocol: TCP
{{- end }}
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
tcpSocket:
port: web
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
successThreshold: {{ .Values.probes.liveness.successThreshold }}
{{- end }}
{{- if .Values.probes.readiness.enabled }}
readinessProbe:
tcpSocket:
port: web
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
successThreshold: {{ .Values.probes.readiness.successThreshold }}
{{- end }}
{{- with .Values.extraEnv }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.persistence.work.enabled }}
- name: work
mountPath: /opt/adguardhome/work
{{- end }}
{{- if .Values.persistence.conf.enabled }}
- name: conf
mountPath: /opt/adguardhome/conf
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
{{- if .Values.persistence.work.enabled }}
- name: work
persistentVolumeClaim:
claimName: {{ .Values.persistence.work.existingClaim | default (printf "%s-work" (include "adguard-home.fullname" .)) }}
{{- end }}
{{- if .Values.persistence.conf.enabled }}
- name: conf
persistentVolumeClaim:
claimName: {{ .Values.persistence.conf.existingClaim | default (printf "%s-conf" (include "adguard-home.fullname" .)) }}
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -2,9 +2,9 @@
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ include "checkmk.fullname" . }} name: {{ include "adguard-home.fullname" . }}
labels: labels:
{{- include "checkmk.labels" . | nindent 4 }} {{- include "adguard-home.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }} {{- with .Values.ingress.annotations }}
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
@ -35,9 +35,9 @@ spec:
pathType: {{ .pathType }} pathType: {{ .pathType }}
backend: backend:
service: service:
name: {{ include "checkmk.fullname" $ }} name: {{ include "adguard-home.fullname" $ }}
port: port:
number: {{ $.Values.service.port }} number: {{ $.Values.ports.web.port }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}

View File

@ -0,0 +1,43 @@
{{- if and .Values.persistence.work.enabled (not .Values.persistence.work.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "adguard-home.fullname" . }}-work
labels:
{{- include "adguard-home.labels" . | nindent 4 }}
{{- with .Values.persistence.work.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
accessModes:
- {{ .Values.persistence.work.accessMode | quote }}
{{- if .Values.persistence.work.storageClass }}
storageClassName: {{ .Values.persistence.work.storageClass | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.work.size | quote }}
{{- end }}
---
{{- if and .Values.persistence.conf.enabled (not .Values.persistence.conf.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "adguard-home.fullname" . }}-conf
labels:
{{- include "adguard-home.labels" . | nindent 4 }}
{{- with .Values.persistence.conf.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
accessModes:
- {{ .Values.persistence.conf.accessMode | quote }}
{{- if .Values.persistence.conf.storageClass }}
storageClassName: {{ .Values.persistence.conf.storageClass | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.conf.size | quote }}
{{- end }}

View File

@ -0,0 +1,76 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "adguard-home.fullname" . }}
labels:
{{- include "adguard-home.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
{{- if .Values.service.loadBalancerIP }}
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}
ports:
- name: web
port: {{ .Values.ports.web.port }}
targetPort: web
protocol: TCP
- name: dns-tcp
port: {{ .Values.ports.dns.port }}
targetPort: dns-tcp
protocol: TCP
- name: dns-udp
port: {{ .Values.ports.dns.port }}
targetPort: dns-udp
protocol: UDP
{{- if .Values.ports.dot.enabled }}
- name: dot-tcp
port: {{ .Values.ports.dot.port }}
targetPort: dot-tcp
protocol: TCP
- name: dot-udp
port: {{ .Values.ports.dot.port }}
targetPort: dot-udp
protocol: UDP
{{- end }}
{{- if .Values.ports.https.enabled }}
- name: https-tcp
port: {{ .Values.ports.https.port }}
targetPort: https-tcp
protocol: TCP
- name: https-udp
port: {{ .Values.ports.https.port }}
targetPort: https-udp
protocol: UDP
{{- end }}
{{- if .Values.ports.dnscrypt.enabled }}
- name: dnscrypt-tcp
port: {{ .Values.ports.dnscrypt.port }}
targetPort: dnscrypt-tcp
protocol: TCP
- name: dnscrypt-udp
port: {{ .Values.ports.dnscrypt.port }}
targetPort: dnscrypt-udp
protocol: UDP
{{- end }}
{{- if .Values.ports.dhcp.enabled }}
- name: dhcp-server
port: 67
targetPort: dhcp-server
protocol: UDP
- name: dhcp-client
port: 68
targetPort: dhcp-client
protocol: UDP
{{- end }}
{{- if .Values.ports.pprof.enabled }}
- name: pprof
port: {{ .Values.ports.pprof.port }}
targetPort: pprof
protocol: TCP
{{- end }}
selector:
{{- include "adguard-home.selectorLabels" . | nindent 4 }}

View File

@ -0,0 +1,161 @@
## Global settings
nameOverride: ""
fullnameOverride: ""
## Image settings
image:
repository: adguard/adguardhome
tag: "v0.107.79"
pullPolicy: IfNotPresent
imagePullSecrets: []
## Deployment settings
## AdGuard Home keeps its state on disk (PVCs below) and is not designed to run as
## multiple replicas against the same data - keep replicaCount at 1.
replicaCount: 1
revisionHistoryLimit: 3
## Pod security settings
## AdGuard Home's own startup check refuses to run on its first launch unless the
## process euid is 0 ("this is the first launch of adguard home; you must run it as
## administrator"), so - unlike most charts here - this one runs as root. Capabilities
## are still dropped to the minimum the binary needs.
podSecurityContext:
runAsNonRoot: false
runAsUser: 0
fsGroup: 0
containerSecurityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
- CHOWN
- DAC_OVERRIDE
- SETUID
- SETGID
## Pod scheduling
nodeSelector: {}
tolerations: []
affinity: {}
podAnnotations: {}
## Use host networking. Required if you enable the DHCP server, and recommended
## when exposing DNS so AdGuard Home sees real client IPs instead of the pod/service IP.
hostNetwork: false
## Ports exposed by AdGuard Home. web+dns are enabled by default; the rest are
## optional protocols you can turn on as needed.
## https://adguard-dns.io/kb/adguard-home/docker/
ports:
web:
# Admin dashboard / setup wizard
port: 3000
dns:
# Plain DNS (TCP+UDP)
port: 53
dot:
# DNS-over-TLS / DNS-over-QUIC (TCP+UDP)
enabled: false
port: 853
https:
# DNS-over-HTTPS and HTTPS admin dashboard (TCP+UDP)
enabled: false
port: 443
dnscrypt:
# DNSCrypt (TCP+UDP)
enabled: false
port: 5443
dhcp:
# DHCP server (UDP 67/68). Requires hostNetwork: true.
enabled: false
pprof:
# Debug pprof API - leave disabled unless troubleshooting
enabled: false
port: 6060
## Service settings
## For exposing DNS to your LAN, set type to LoadBalancer (e.g. with MetalLB) and
## add annotations/loadBalancerIP as needed, or use type: NodePort.
service:
type: ClusterIP
annotations: {}
# loadBalancerIP: 192.168.1.53
## Ingress settings (routes to the admin web UI only)
ingress:
enabled: false
className: ""
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
hosts:
- host: adguard.domain.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- adguard.domain.com
## Persistence settings
persistence:
work:
# /opt/adguardhome/work - query log, filter cache, stats
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 1Gi
annotations: {}
conf:
# /opt/adguardhome/conf - AdGuardHome.yaml configuration
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 100Mi
annotations: {}
## Resource limits and requests
resources: {}
# resources:
# limits:
# cpu: 500m
# memory: 256Mi
# requests:
# cpu: 50m
# memory: 64Mi
## Application health checks
## Uses a TCP check against the admin web port. The DNS listener only comes up after
## the first-run setup wizard is completed, so probing the DNS port instead would
## fail/restart the pod before it's ever configured; the web port is always up.
probes:
liveness:
enabled: true
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
readiness:
enabled: true
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
## Extra environment variables
extraEnv: []
## Extra volume mounts
extraVolumeMounts: []
## Extra volumes
extraVolumes: []

View File

@ -1,18 +0,0 @@
apiVersion: v2
name: checkmk
description: Checkmk monitoring platform helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "2.5.0p6"
maintainers:
- name: Richard Tomik
email: richard.tomik@proton.me
keywords:
- monitoring
- checkmk
- infrastructure
- observability
home: https://github.com/rtomik/helm-charts
sources:
- https://checkmk.com
- https://hub.docker.com/r/checkmk/check-mk-community

View File

@ -1,56 +0,0 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "checkmk.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "checkmk.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "checkmk.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "checkmk.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:5000/{{ .Values.config.siteId }}/check_mk/ to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 5000:5000
{{- end }}
2. Checkmk web interface is available at:
http://<host>/{{ .Values.config.siteId }}/check_mk/
3. Default credentials:
Username: cmkadmin
Password: (as configured in config.adminPassword)
4. Ports:
- Web interface: {{ .Values.service.port }} → container 5000
- Agent Receiver: {{ .Values.service.agentReceiverPort }} → container 8000
{{- if .Values.persistence.enabled }}
5. Persistent storage: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{ else }}{{ include "checkmk.fullname" . }}-sites{{ end }} ({{ .Values.persistence.size }})
Mounted at /omd/sites — contains all site data, configs, and RRD files.
{{- else }}
5. WARNING: No persistent storage enabled. All monitoring data will be lost on pod restart.
Enable persistence in values.yaml for production use.
{{- end }}
{{- if .Values.config.livestatusTcp }}
6. Livestatus TCP is enabled. Ensure appropriate NetworkPolicies are in place.
{{- end }}
{{- if not .Values.config.adminPassword.existingSecret }}
7. SECURITY NOTE: For production use, store the admin password in a Kubernetes Secret:
kubectl create secret generic checkmk-secrets \
--from-literal=cmk-password=<your-password>
Then set config.adminPassword.existingSecret=checkmk-secrets in your values.
{{- else }}
7. Admin password read from existing secret: {{ .Values.config.adminPassword.existingSecret }}
{{- end }}
For more information, see the official Checkmk Docker documentation:
https://docs.checkmk.com/latest/en/introduction_docker.html

View File

@ -1,91 +0,0 @@
# Checkmk Helm Chart
Helm chart for deploying [Checkmk](https://checkmk.com/) — an infrastructure and application monitoring platform — on Kubernetes.
## Overview
Checkmk uses OMD (Open Monitoring Distribution) to manage monitoring sites. This chart deploys the Community Edition using the official Docker image with:
- Persistent storage for all site data (`/omd/sites`)
- RAM-backed tmpfs for the site temp directory (performance optimization from the official docs)
- Separate service ports for the web interface (5000) and agent receiver (8000)
- Admin password stored in a Kubernetes Secret
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- A default StorageClass or an existing PersistentVolumeClaim
## Quick Start
```bash
helm install checkmk ./charts/checkmk \
--set config.adminPassword.value=mysecretpassword \
--set ingress.enabled=true \
--set ingress.hosts[0].host=checkmk.example.com \
--set ingress.hosts[0].paths[0].path=/ \
--set ingress.hosts[0].paths[0].pathType=Prefix
```
After installation, access the UI at `http://<host>/cmk/check_mk/` with username `cmkadmin`.
## Configuration
### Admin Password (recommended: use existing secret)
```bash
kubectl create secret generic checkmk-secrets \
--from-literal=cmk-password=<your-password>
```
```yaml
config:
adminPassword:
existingSecret: "checkmk-secrets"
passwordKey: "cmk-password"
```
### Site ID
The `config.siteId` value sets the Checkmk site name and determines the URL path (`/<siteId>/check_mk/`). Defaults to `cmk`.
### Livestatus TCP
Enable Livestatus TCP for distributed monitoring setups or external integrations:
```yaml
config:
livestatusTcp: true
```
### Persistence
Site data (hosts, checks, RRD files) is stored in `/omd/sites`. A 5 Gi PVC is created by default. Adjust size or use an existing claim:
```yaml
persistence:
size: 20Gi
storageClass: "fast-ssd"
```
## Key Values
| Key | Default | Description |
|-----|---------|-------------|
| `image.tag` | `2.5.0p6` | Checkmk Community image tag |
| `config.siteId` | `cmk` | Monitoring site name |
| `config.timezone` | `UTC` | Container timezone |
| `config.adminPassword.value` | `changeme` | cmkadmin password (use existingSecret in production) |
| `config.livestatusTcp` | `false` | Enable Livestatus over TCP |
| `config.mailRelayHost` | `""` | SMTP relay for notifications |
| `service.port` | `5000` | Web interface port |
| `service.agentReceiverPort` | `8000` | Agent registration port |
| `persistence.enabled` | `true` | Enable persistent storage |
| `persistence.size` | `5Gi` | PVC size |
## Security Notes
Checkmk (OMD) requires root access inside the container to manage monitoring sites and switch to the site user account. The `podSecurityContext.runAsUser` is set to `0` and `containerSecurityContext.allowPrivilegeEscalation` is `true` by default.
Place a reverse proxy (e.g. Traefik or nginx) in front for TLS termination rather than exposing the container port directly.

View File

@ -1,135 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "checkmk.fullname" . }}
labels:
{{- include "checkmk.labels" . | nindent 4 }}
annotations:
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
spec:
replicas: {{ .Values.replicaCount }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
selector:
matchLabels:
{{- include "checkmk.selectorLabels" . | nindent 6 }}
strategy:
type: Recreate
template:
metadata:
labels:
{{- include "checkmk.selectorLabels" . | nindent 8 }}
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 5000
protocol: TCP
- name: agent-receiver
containerPort: 8000
protocol: TCP
{{- if .Values.probes.startup.enabled }}
startupProbe:
httpGet:
path: {{ include "checkmk.probePath" . }}
port: http
initialDelaySeconds: {{ .Values.probes.startup.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.startup.periodSeconds }}
timeoutSeconds: {{ .Values.probes.startup.timeoutSeconds }}
failureThreshold: {{ .Values.probes.startup.failureThreshold }}
successThreshold: {{ .Values.probes.startup.successThreshold }}
{{- end }}
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
httpGet:
path: {{ include "checkmk.probePath" . }}
port: http
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
successThreshold: {{ .Values.probes.liveness.successThreshold }}
{{- end }}
{{- if .Values.probes.readiness.enabled }}
readinessProbe:
httpGet:
path: {{ include "checkmk.probePath" . }}
port: http
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
successThreshold: {{ .Values.probes.readiness.successThreshold }}
{{- end }}
env:
- name: CMK_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "checkmk.secretName" . }}
key: {{ .Values.config.adminPassword.passwordKey }}
- name: CMK_SITE_ID
value: {{ .Values.config.siteId | quote }}
- name: TZ
value: {{ .Values.config.timezone | quote }}
{{- if .Values.config.livestatusTcp }}
- name: CMK_LIVESTATUS_TCP
value: "on"
{{- end }}
{{- if .Values.config.mailRelayHost }}
- name: MAIL_RELAY_HOST
value: {{ .Values.config.mailRelayHost | quote }}
{{- end }}
{{- with .Values.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: sites
mountPath: /omd/sites
# tmpfs for site temp dir — improves performance by using host RAM
# equivalent to Docker's --tmpfs /opt/omd/sites/<siteId>/tmp:uid=1000,gid=1000
- name: tmp
mountPath: {{ include "checkmk.tmpPath" . }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
{{- if .Values.persistence.enabled }}
- name: sites
persistentVolumeClaim:
claimName: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{ else }}{{ include "checkmk.fullname" . }}-sites{{ end }}
{{- else }}
- name: sites
emptyDir: {}
{{- end }}
- name: tmp
emptyDir:
medium: Memory
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -1,21 +0,0 @@
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "checkmk.fullname" . }}-sites
labels:
{{- include "checkmk.labels" . | nindent 4 }}
{{- with .Values.persistence.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
accessModes:
- {{ .Values.persistence.accessMode | quote }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- end }}

View File

@ -1,11 +0,0 @@
{{- if not .Values.config.adminPassword.existingSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "checkmk.fullname" . }}-secrets
labels:
{{- include "checkmk.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.config.adminPassword.passwordKey }}: {{ .Values.config.adminPassword.value | default "changeme" | b64enc }}
{{- end }}

View File

@ -1,25 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "checkmk.fullname" . }}
labels:
{{- include "checkmk.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
- port: {{ .Values.service.agentReceiverPort }}
targetPort: agent-receiver
protocol: TCP
name: agent-receiver
{{- if .Values.config.livestatusTcp }}
- port: 6557
targetPort: 6557
protocol: TCP
name: livestatus
{{- end }}
selector:
{{- include "checkmk.selectorLabels" . | nindent 4 }}

View File

@ -1,131 +0,0 @@
## Global settings
nameOverride: ""
fullnameOverride: ""
## Image settings
image:
repository: checkmk/check-mk-community
tag: "2.5.0p6"
pullPolicy: IfNotPresent
## Deployment settings
replicaCount: 1
revisionHistoryLimit: 3
# Pod security settings
# Checkmk (OMD) requires root to manage monitoring sites and switch to site users.
# fsGroup: 1000 matches Docker's --tmpfs uid=1000,gid=1000 so the site user can write to tmp.
podSecurityContext:
runAsNonRoot: false
runAsUser: 0
fsGroup: 1000
containerSecurityContext:
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
## Pod scheduling
nodeSelector: {}
tolerations: []
affinity: {}
## Pod annotations
podAnnotations: {}
## Service settings
service:
type: ClusterIP
port: 5000
agentReceiverPort: 8000
## Ingress settings
ingress:
enabled: false
className: ""
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
hosts:
- host: checkmk.domain.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- checkmk.domain.com
# secretName: "existing-tls-secret"
## Persistence settings for /omd/sites (all site data, configs, and RRDs)
persistence:
enabled: true
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce
size: 5Gi
annotations: {}
## Resource limits and requests
# resources:
# limits:
# cpu: 2000m
# memory: 2Gi
# requests:
# cpu: 500m
# memory: 512Mi
## Application health checks
# startupProbe absorbs slow first-boot (site init + DB creation) so liveness/readiness
# don't fire until the site is actually up. Budget: 120 * 10s = 20 minutes max.
probes:
startup:
enabled: true
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 120
successThreshold: 1
liveness:
enabled: true
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 6
successThreshold: 1
readiness:
enabled: true
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
## Checkmk configuration
config:
# Site name — also determines the URL path: /<siteId>/check_mk/
siteId: "cmk"
# Timezone (e.g. Europe/Berlin)
timezone: "UTC"
# Enable Livestatus TCP access (for distributed monitoring or external tools).
# When enabled, port 6557 is added to the Service.
livestatusTcp: false
# SMTP relay host for notifications (leave empty to disable)
mailRelayHost: ""
## Admin (cmkadmin) password
adminPassword:
# Use an existing Kubernetes secret
existingSecret: ""
passwordKey: "cmk-password"
# Or set directly (not recommended for production)
value: "changeme"
# Extra environment variables
extraEnv: []
# - name: CMK_LIVESTATUS_TCP
# value: "on"
# Extra volume mounts
extraVolumeMounts: []
# Extra volumes
extraVolumes: []

View File

@ -2,8 +2,8 @@ apiVersion: v2
name: norish name: norish
description: Norish helm chart for Kubernetes - A recipe management and meal planning application description: Norish helm chart for Kubernetes - A recipe management and meal planning application
type: application type: application
version: 0.0.5 version: 0.0.6
appVersion: "v0.15.4-beta" appVersion: "v0.20.0-beta"
maintainers: maintainers:
- name: Richard Tomik - name: Richard Tomik
email: no@m.com email: no@m.com
@ -14,4 +14,4 @@ keywords:
- norish - norish
home: https://github.com/rtomik/helm-charts home: https://github.com/rtomik/helm-charts
sources: sources:
- https://github.com/norishapp/norish - https://github.com/norish-recipes/norish

View File

@ -1,6 +1,6 @@
# Norish Helm Chart # Norish Helm Chart
A Helm chart for deploying [Norish](https://github.com/norishapp/norish), a recipe management and meal planning application, on Kubernetes. A Helm chart for deploying [Norish](https://github.com/norish-recipes/norish), a recipe management and meal planning application, on Kubernetes.
## Introduction ## Introduction
@ -112,6 +112,24 @@ config:
passwordAuthEnabled: "true" passwordAuthEnabled: "true"
``` ```
Set the redirect URI in your provider to:
`https://norish.example.com/api/auth/oauth2/callback/oidc`
Optionally map OIDC group claims to the admin role and to households:
```yaml
config:
auth:
oidc:
enabled: true
claimMapping:
enabled: true
scopes: "groups"
groupsClaim: "groups"
adminGroup: "norish_admin"
householdGroupPrefix: "norish_household_"
```
### GitHub OAuth ### GitHub OAuth
1. Create a GitHub OAuth App at https://github.com/settings/developers 1. Create a GitHub OAuth App at https://github.com/settings/developers
@ -148,6 +166,30 @@ persistence:
existingClaim: "my-existing-pvc" existingClaim: "my-existing-pvc"
``` ```
### Adopting an Existing (non-Helm) Norish Install
Resource names are derived from the chart name, not the release name, so this chart always
creates `norish-secret` and `norish-uploads`. If you already run Norish from hand-written
manifests using those same names, point the chart at the existing objects instead of
letting it template new ones:
```yaml
config:
masterKey:
existingSecret: "norish-secret" # reuse the existing key
secretKey: "master-key"
persistence:
existingClaim: "norish-uploads"
redis:
existingSecret: "norish-secret"
urlKey: "redis-url"
```
⚠️ **Never let a new `MASTER_KEY` be generated for an existing database.** The key derives
the encryption keys, so replacing it makes every previously encrypted value unreadable.
Helm refuses to adopt resources it does not own, but a GitOps tool configured to replace or
prune resources will not stop you — set `existingSecret` before the first sync.
## Parameters ## Parameters
### Global Parameters ### Global Parameters
@ -162,7 +204,7 @@ persistence:
| Name | Description | Default | | Name | Description | Default |
|------|-------------|---------| |------|-------------|---------|
| `image.repository` | Norish image repository | `norishapp/norish` | | `image.repository` | Norish image repository | `norishapp/norish` |
| `image.tag` | Image tag | `v0.15.4-beta` | | `image.tag` | Image tag | `v0.20.0-beta` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` | | `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets | `[]` | | `imagePullSecrets` | Image pull secrets | `[]` |
@ -245,6 +287,11 @@ persistence:
| `config.logLevel` | Log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) | `""` | | `config.logLevel` | Log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) | `""` |
| `config.trustedOrigins` | Additional trusted origins (comma-separated) | `""` | | `config.trustedOrigins` | Additional trusted origins (comma-separated) | `""` |
| `config.passwordAuthEnabled` | Enable/disable password auth | `""` | | `config.passwordAuthEnabled` | Enable/disable password auth | `""` |
| `config.enableRegistration` | Allow self-registration of new users | `""` |
| `config.uploadsDir` | Uploads directory / volume mount path (`UPLOADS_DIR`) | `/app/uploads` |
| `config.parserApiTimeoutMs` | Recipe parser API timeout in ms | `""` |
| `config.defaultLocale` | Instance default locale | `""` |
| `config.enabledLocales` | Comma-separated list of enabled locales (empty = all) | `""` |
| `config.extraEnv` | Extra environment variables | `[]` | | `config.extraEnv` | Extra environment variables | `[]` |
### Master Key Configuration (Required) ### Master Key Configuration (Required)
@ -270,6 +317,11 @@ Generate with: `openssl rand -base64 32`
| `config.auth.oidc.existingSecret` | Existing secret name | `""` | | `config.auth.oidc.existingSecret` | Existing secret name | `""` |
| `config.auth.oidc.clientIdKey` | Key for client ID in secret | `oidc-client-id` | | `config.auth.oidc.clientIdKey` | Key for client ID in secret | `oidc-client-id` |
| `config.auth.oidc.clientSecretKey` | Key for client secret in secret | `oidc-client-secret` | | `config.auth.oidc.clientSecretKey` | Key for client secret in secret | `oidc-client-secret` |
| `config.auth.oidc.claimMapping.enabled` | Assign admin role / households from OIDC claims | `false` |
| `config.auth.oidc.claimMapping.scopes` | Extra scopes to request (comma-separated) | `""` |
| `config.auth.oidc.claimMapping.groupsClaim` | Claim containing user groups | `groups` |
| `config.auth.oidc.claimMapping.adminGroup` | Group granting the server admin role | `norish_admin` |
| `config.auth.oidc.claimMapping.householdGroupPrefix` | Prefix for household groups | `norish_household_` |
### GitHub OAuth ### GitHub OAuth
@ -316,13 +368,16 @@ Generate with: `openssl rand -base64 32`
| Name | Description | Default | | Name | Description | Default |
|------|-------------|---------| |------|-------------|---------|
| `probes.startup.enabled` | Enable startup probe | `true` | | `probes.startup.enabled` | Enable startup probe | `true` |
| `probes.startup.path` | Startup probe path | `/api/v1/health` |
| `probes.startup.initialDelaySeconds` | Startup initial delay | `10` | | `probes.startup.initialDelaySeconds` | Startup initial delay | `10` |
| `probes.startup.periodSeconds` | Startup period | `10` | | `probes.startup.periodSeconds` | Startup period | `10` |
| `probes.startup.failureThreshold` | Startup failure threshold | `30` | | `probes.startup.failureThreshold` | Startup failure threshold | `30` |
| `probes.liveness.enabled` | Enable liveness probe | `true` | | `probes.liveness.enabled` | Enable liveness probe | `true` |
| `probes.liveness.path` | Liveness probe path (see [Upgrading](#upgrading) for why this is not the health endpoint) | `/` |
| `probes.liveness.initialDelaySeconds` | Liveness initial delay | `30` | | `probes.liveness.initialDelaySeconds` | Liveness initial delay | `30` |
| `probes.liveness.periodSeconds` | Liveness period | `10` | | `probes.liveness.periodSeconds` | Liveness period | `10` |
| `probes.readiness.enabled` | Enable readiness probe | `true` | | `probes.readiness.enabled` | Enable readiness probe | `true` |
| `probes.readiness.path` | Readiness probe path | `/api/v1/health` |
| `probes.readiness.initialDelaySeconds` | Readiness initial delay | `5` | | `probes.readiness.initialDelaySeconds` | Readiness initial delay | `5` |
| `probes.readiness.periodSeconds` | Readiness period | `5` | | `probes.readiness.periodSeconds` | Readiness period | `5` |
@ -336,13 +391,102 @@ Generate with: `openssl rand -base64 32`
No configuration changes required. Redis, PostgreSQL, and Chrome headless are already configured. Back up your database before upgrading as a precaution. No configuration changes required. Redis, PostgreSQL, and Chrome headless are already configured. Back up your database before upgrading as a precaution.
### From chart 0.0.5 to chart 0.0.6 (app v0.20.0-beta)
⚠️ **Back up your database and your uploads volume before upgrading.**
**Which app version were you on?** Chart 0.0.5 declared `appVersion: v0.15.4-beta` but
shipped `image.tag: v0.16.2-beta` in values.yaml, and the tag always wins. So unless you
pinned `image.tag` yourself, you were already running **v0.16.2-beta** and the two
v0.16.x data-loss items below have already happened to you — skip them. Chart 0.0.6 fixes
that mismatch: both `appVersion` and `image.tag` are now `v0.20.0-beta`.
**v0.16.0-beta — data loss (calendar) — only if you pinned `image.tag` to v0.15.x or older**
All calendar data is permanently deleted on upgrade. The calendar was rebuilt on a new
database schema and upstream provides no migration path. Recipes, groceries and planning
data outside the calendar are unaffected.
**v0.16.1-beta — data loss (custom units) — only if you pinned `image.tag` to v0.16.0 or older**
Custom UOM (unit of measure) data is wiped as part of the move to a locale-aware schema.
Custom units have to be re-created after the upgrade.
The remaining items apply to everyone upgrading from chart 0.0.5.
**v0.17.0-beta — image restructure**
Upstream migrated to a pnpm/Turborepo monorepo. The Docker image, its internal paths and
the package layout all changed. The image name is unchanged (`norishapp/norish`), and this
chart needs no value changes for it, but the release is explicitly flagged as
"back up your data before upgrading" by upstream.
**v0.18.0-beta — breaking: health endpoint moved**
The previous `/api/health` endpoint was removed; the endpoint is now `/api/v1/health`.
Verified on both versions: on v0.17.3-beta `/api/health` returns `{"status":"ok"}`, on
v0.20.0-beta it falls through to the auth redirect. Note that a removed API path returns a
**307 redirect**, not a 404 — so an HTTP probe or uptime check still pointing at
`/api/health` reports *success* while checking nothing at all. Repoint it explicitly.
This chart's probe defaults changed accordingly:
| Value | Old default | New default |
|-------|-------------|-------------|
| `probes.startup.path` | `/` | `/api/v1/health` |
| `probes.readiness.path` | `/` | `/api/v1/health` |
| `probes.liveness.path` | `/` | `/` (unchanged — see below) |
If you pinned these paths in your own values, update them. Any external uptime monitor or
ingress health check pointing at the old endpoint must be updated as well.
**Why liveness deliberately does not use the health endpoint.** Since v0.18.1-beta the
endpoint also reports database health, and it returns **503** when PostgreSQL is
unreachable — verified on a test cluster by scaling the database to zero. With liveness
pointed at it, the sequence is:
1. Six consecutive 503s fail the liveness probe and the kubelet restarts the container.
2. On restart the app runs its migrations, cannot reach the database, and exits 1.
3. The pod enters `CrashLoopBackOff`, so it stays down for the backoff interval even
after the database comes back.
A short database blip therefore becomes a multi-minute outage. With liveness on `/` (which
returns a 307 redirect — a probe success) a running pod rides out a DB blip: readiness
still fails, so the pod is removed from the Service endpoints, and it serves again as soon
as the database returns, with no restart.
Note that the app cannot start at all without a reachable database, by design — it runs
migrations at boot and exits on failure. Liveness on the app root does not hide that; it
only avoids restarting a process that is alive and would otherwise recover on its own.
**v0.18.0-beta — recipe import pipeline**
Imports moved to the `recipe-scrapers` Python package. The Chrome headless sidecar is
still required (upstream still ships it and still lists `CHROME_WS_ENDPOINT` as a core
required setting), so leave `chrome.enabled: true`. Import timeouts can be tuned with the
new `config.parserApiTimeoutMs`.
**Migration path — tested**
The v0.17.3-beta → v0.20.0-beta upgrade was verified on a Kubernetes test cluster against
a schema created by v0.17.3-beta: migrations applied automatically at boot (31 → 40
applied migrations, 29 → 34 tables), with no manual steps and no errors. The app applies
migrations itself on startup; there is nothing to run by hand.
**v0.19.0-beta / v0.20.0-beta — no configuration changes**
Web app refresh (HeroUI v3, home screen, cooking mode), offline support, recipe
provenance and AI workflow improvements. Nothing to change in this chart.
**New chart values in this release** (all optional, all default to the previous behaviour):
`config.enableRegistration`, `config.uploadsDir`, `config.parserApiTimeoutMs`,
`config.defaultLocale`, `config.enabledLocales` and `config.auth.oidc.claimMapping.*`.
The Chrome sidecar also now passes `--disable-features=dbus`, matching the upstream Docker
Compose example.
## Troubleshooting ## Troubleshooting
- **Master Key Not Set**: Generate with `openssl rand -base64 32` - **Master Key Not Set**: Generate with `openssl rand -base64 32`
- **Login Failures**: Password auth is enabled by default when no OAuth/OIDC is configured. Verify callback URLs match your ingress hostname. - **Login Failures**: Password auth is enabled by default when no OAuth/OIDC is configured. Verify callback URLs match your ingress hostname.
- **Database Connection Failed**: Verify host, credentials, and that the database exists. - **Database Connection Failed**: Verify host, credentials, and that the database exists.
- **Chrome Headless Issues**: Chrome requires `SYS_ADMIN` capability and 256Mi-512Mi memory. Check logs with `kubectl logs -l app.kubernetes.io/name=norish -c chrome-headless` - **Chrome Headless Issues**: Chrome requires `SYS_ADMIN` capability and 256Mi-512Mi memory. Check logs with `kubectl logs -l app.kubernetes.io/name=norish -c chrome-headless`
- **Recipe Parsing Failures**: Ensure Chrome is running. `CHROME_WS_ENDPOINT` is automatically configured by the chart. - **Recipe Parsing Failures**: Ensure Chrome is running. `CHROME_WS_ENDPOINT` is automatically configured by the chart. Raise `config.parserApiTimeoutMs` if imports time out.
- **Pod Never Becomes Ready After Upgrade**: On v0.18.0+ the health endpoint is `/api/v1/health`. Probes still pointing at the old endpoint will fail. Check with `kubectl exec deploy/norish -c norish -- wget -qO- http://127.0.0.1:3000/api/v1/health` (use `127.0.0.1`, not `localhost` — that resolves to `::1` in the container and is refused). A healthy response reports `status`, `db.status`, the app version and the `recipe-scrapers` version.
- **CrashLoopBackOff With "Migration failed" / "Server startup failed"**: The app runs migrations at boot and exits if PostgreSQL is unreachable. Verify `database.host`, credentials and that the database exists; the pod recovers on its own once the database is reachable.
```bash ```bash
kubectl get pods -l app.kubernetes.io/name=norish kubectl get pods -l app.kubernetes.io/name=norish
@ -351,5 +495,7 @@ kubectl logs -l app.kubernetes.io/name=norish
## Links ## Links
- [Norish GitHub](https://github.com/norishapp/norish) - [Norish GitHub](https://github.com/norish-recipes/norish)
- [Norish Documentation](https://docs.norish.dev)
- [Norish Releases](https://github.com/norish-recipes/norish/releases)
- [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/norish) - [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/norish)

View File

@ -71,4 +71,4 @@ IMPORTANT CONFIGURATION NOTES:
Configure ONE provider (OIDC, GitHub, or Google) to create your admin account. Configure ONE provider (OIDC, GitHub, or Google) to create your admin account.
{{- end }} {{- end }}
For more information, visit: https://github.com/norishapp/norish For more information, visit: https://github.com/norish-recipes/norish

View File

@ -96,6 +96,24 @@ spec:
- name: PASSWORD_AUTH_ENABLED - name: PASSWORD_AUTH_ENABLED
value: {{ .Values.config.passwordAuthEnabled | quote }} value: {{ .Values.config.passwordAuthEnabled | quote }}
{{- end }} {{- end }}
{{- if .Values.config.enableRegistration }}
- name: ENABLE_REGISTRATION
value: {{ .Values.config.enableRegistration | quote }}
{{- end }}
- name: UPLOADS_DIR
value: {{ .Values.config.uploadsDir | quote }}
{{- if .Values.config.parserApiTimeoutMs }}
- name: PARSER_API_TIMEOUT_MS
value: {{ .Values.config.parserApiTimeoutMs | quote }}
{{- end }}
{{- if .Values.config.defaultLocale }}
- name: DEFAULT_LOCALE
value: {{ .Values.config.defaultLocale | quote }}
{{- end }}
{{- if .Values.config.enabledLocales }}
- name: ENABLED_LOCALES
value: {{ .Values.config.enabledLocales | quote }}
{{- end }}
{{- if .Values.database.existingSecret }} {{- if .Values.database.existingSecret }}
- name: DB_USERNAME - name: DB_USERNAME
valueFrom: valueFrom:
@ -164,6 +182,20 @@ spec:
- name: OIDC_WELLKNOWN - name: OIDC_WELLKNOWN
value: {{ .Values.config.auth.oidc.wellKnown | quote }} value: {{ .Values.config.auth.oidc.wellKnown | quote }}
{{- end }} {{- end }}
{{- if .Values.config.auth.oidc.claimMapping.enabled }}
- name: OIDC_CLAIM_MAPPING_ENABLED
value: "true"
{{- with .Values.config.auth.oidc.claimMapping.scopes }}
- name: OIDC_SCOPES
value: {{ . | quote }}
{{- end }}
- name: OIDC_GROUPS_CLAIM
value: {{ .Values.config.auth.oidc.claimMapping.groupsClaim | quote }}
- name: OIDC_ADMIN_GROUP
value: {{ .Values.config.auth.oidc.claimMapping.adminGroup | quote }}
- name: OIDC_HOUSEHOLD_GROUP_PREFIX
value: {{ .Values.config.auth.oidc.claimMapping.householdGroupPrefix | quote }}
{{- end }}
- name: OIDC_CLIENT_ID - name: OIDC_CLIENT_ID
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@ -234,7 +266,7 @@ spec:
{{- end }} {{- end }}
volumeMounts: volumeMounts:
- name: uploads - name: uploads
mountPath: /app/uploads mountPath: {{ .Values.config.uploadsDir }}
{{- with .Values.extraVolumeMounts }} {{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }} {{- toYaml . | nindent 12 }}
{{- end }} {{- end }}
@ -256,6 +288,7 @@ spec:
- "--no-sandbox" - "--no-sandbox"
- "--disable-gpu" - "--disable-gpu"
- "--disable-dev-shm-usage" - "--disable-dev-shm-usage"
- "--disable-features=dbus"
- "--remote-debugging-address=0.0.0.0" - "--remote-debugging-address=0.0.0.0"
- "--remote-debugging-port={{ .Values.chrome.port }}" - "--remote-debugging-port={{ .Values.chrome.port }}"
- "--headless" - "--headless"

View File

@ -5,7 +5,7 @@ fullnameOverride: ""
## Image settings ## Image settings
image: image:
repository: norishapp/norish repository: norishapp/norish
tag: "v0.16.2-beta" tag: "v0.20.0-beta"
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
imagePullSecrets: [] imagePullSecrets: []
@ -96,7 +96,7 @@ probes:
timeoutSeconds: 5 timeoutSeconds: 5
failureThreshold: 30 failureThreshold: 30
successThreshold: 1 successThreshold: 1
path: / path: /api/v1/health
liveness: liveness:
enabled: true enabled: true
initialDelaySeconds: 30 initialDelaySeconds: 30
@ -104,6 +104,11 @@ probes:
timeoutSeconds: 5 timeoutSeconds: 5
failureThreshold: 6 failureThreshold: 6
successThreshold: 1 successThreshold: 1
# Deliberately NOT /api/v1/health: since v0.18.1 that endpoint returns 503 when the
# database is unreachable, so a transient DB outage would restart the pod. The app
# root confirms the process is alive without coupling liveness to the database.
# Readiness still uses the health endpoint, which is what pulls the pod out of the
# Service during a DB outage.
path: / path: /
readiness: readiness:
enabled: true enabled: true
@ -112,7 +117,7 @@ probes:
timeoutSeconds: 3 timeoutSeconds: 3
failureThreshold: 3 failureThreshold: 3
successThreshold: 1 successThreshold: 1
path: / path: /api/v1/health
## Application configuration ## Application configuration
config: config:
@ -154,6 +159,26 @@ config:
# Defaults to false if OIDC or OAuth is configured, true otherwise # Defaults to false if OIDC or OAuth is configured, true otherwise
passwordAuthEnabled: "" passwordAuthEnabled: ""
# Allow new users to register themselves (ENABLE_REGISTRATION)
# Leave empty to use the application default (false).
# The first account created always becomes the server owner/admin.
enableRegistration: ""
# Uploads directory inside the container (UPLOADS_DIR)
# Also used as the mount path for the uploads volume
uploadsDir: "/app/uploads"
# Timeout for recipe parser API calls in milliseconds (PARSER_API_TIMEOUT_MS)
# Leave empty to use the application default (15000)
parserApiTimeoutMs: ""
# Instance default locale, must match a supported locale (DEFAULT_LOCALE)
defaultLocale: ""
# Comma-separated list of enabled locales (ENABLED_LOCALES)
# Leave empty to enable all locales. Example: "en,de,nl"
enabledLocales: ""
# Authentication provider configuration # Authentication provider configuration
# Configure ONE provider for initial admin account creation # Configure ONE provider for initial admin account creation
# After first login, manage additional providers via Settings → Admin # After first login, manage additional providers via Settings → Admin
@ -173,6 +198,16 @@ config:
clientIdKey: "oidc-client-id" clientIdKey: "oidc-client-id"
clientSecretKey: "oidc-client-secret" clientSecretKey: "oidc-client-secret"
# Claim mapping: auto-assign the admin role and households from OIDC claims
# Disabled by default for security
claimMapping:
enabled: false
# Additional scopes to request, comma-separated (e.g. "groups" for Keycloak)
scopes: ""
groupsClaim: "groups" # Claim containing the user groups
adminGroup: "norish_admin" # Group that grants the server admin role
householdGroupPrefix: "norish_household_" # Prefix for household groups
# GitHub OAuth # GitHub OAuth
github: github:
enabled: false enabled: false