Reference
Helm values
Every option of the Kubernetes chart, generated from the chart's own values.yaml and its comments, so it can't drift from the chart.
- The whole file, with its comments, for the version you install:
helm show values oci://ghcr.io/paimonsoror/charts/telltale > values.yaml # or one version: --version X.Y.Z (builds from main: --devel, or --version X.Y.Z-edge.N) - The file on GitHub:
deploy/helm/telltale/values.yaml. - Keep only what you change in your own file and install with
-f my-values.yaml. The chart checks values against its schema, so a mistyped value fails the install with the key's path. - TelltaleDNS's own settings go in
config:as TOML; every key is in the configuration reference.
Sections: cluster · resolvers · image · kubernetes · service · encrypted · ingress · persistence · resources · auth · serviceMonitor · prometheusRule · grafanaDashboard · networkPolicy · podSecurityContext · securityContext
Top level
| Key | Default | Meaning |
|---|---|---|
mode | allInOne | Shape. - allInOne: one StatefulSet (resolver, API, UI, query log). - scaled (CLU-009): that StatefulSet becomes the cluster's controller (primary, with the volume), plus resolvers.replicas resolver pods. The pods join the controller automatically with a generated Secret, take its configuration and lists, ship their query log to it, and come and go freely (ephemeral members). DNS is spread over every pod; the UI and API stay on the controller. A node outside Kubernetes (a Pi) can join too: see cluster.advertise. - daemonSet (host-network resolvers) comes later. |
imagePullSecrets | [] | |
nameOverride | "" | |
fullnameOverride | "" | |
config | | Extra telltale.toml, merged after the settings the chart controls (listeners, data_dir, metrics and API addresses). See docs/configuration.md. |
hostNetwork | false | Bind DNS on the node's network directly (port 53): real client IPs and MAC addresses, no NAT, no LoadBalancer needed. Binding 53 on the host needs root with only NET_BIND_SERVICE (all other capabilities dropped). On k3s this is fine (CoreDNS listens on cluster IPs only); stop systemd-resolved's stub listener on the node first. |
dnsPort | 5353 | Container port for DNS when not on the host network (the Service maps 53 to it). |
secretMounts | [] | Secrets mounted read-only at /etc/telltale-secrets/<name>, e.g. an OIDC client secret (point client_secret_file at /etc/telltale-secrets/oidc/client_secret). optional: true lets the pod start without the Secret. |
extraEnv | [] | |
podAnnotations | {} | |
podLabels | {} | |
nodeSelector | {} | |
tolerations | [] | |
affinity | {} | |
priorityClassName | "" | |
terminationGracePeriodSeconds | 30 | Draining: readiness turns off first, then TCP and in-flight upstream queries finish. |
drainDelaySeconds | 5 | On shutdown, pods keep answering this long after turning not ready, so the Service stops sending them queries first (no lost lookups during rollouts). Keep it well under terminationGracePeriodSeconds. |
cluster
Clustering (mode: scaled).
| Key | Default | Meaning |
|---|---|---|
site | k8s | Site label for these pods (the Cluster page groups resolver pods by site). |
port | 9443 | The cluster port inside the pods (mTLS between nodes). |
service.type | ClusterIP | ClusterIP for pods only. LoadBalancer (or NodePort) lets a node outside Kubernetes join. |
service.annotations | {} | |
advertise | [] | Extra URLs for the controller's cluster port that nodes outside Kubernetes dial, e.g. ["https://192.168.5.100:9443"] behind a LoadBalancer. In-cluster URLs are added for you. |
bootstrapSecret | The shared join secret. Empty: the chart creates one (kept across upgrades). With Argo CD (no lookup), create the Secret yourself and name it here. | |
bootstrapSecret.existingSecret | "" | A Secret of your own instead of the generated one (48 random characters). Use at least 32 random characters: anyone who knows it can join as a resolver pod. |
bootstrapSecret.key | bootstrap | |
rollout | Staged rollouts (mode: scaled): a configuration change reaches the canary nodes (and the controller, which runs it at once) first, and every other node after bakeSeconds if nothing fails; a failure pins the cluster to the version before. Empty (the default): changes reach every node at once. Entries are node IDs, site:<name>, or ephemeral (the resolver pods). With [ephemeral], the pods take a change at once under the guard's watch and nodes outside Kubernetes (a Raspberry Pi that joined) wait out the bake. Also editable in the web UI: Settings → Cluster → Staged rollouts. | |
rollout.canaries | [] | |
rollout.bakeSeconds | 300 | How long a change bakes on the canaries before everyone gets it (at least 5). |
rollout.servfailPct | 5 | Fail when the canaries' SERVFAIL share during the bake is over max(the share before + 2 points, this) percent (with at least 50 answers). |
rollout.requireTraffic | false | Keep baking until the canaries answered 50 queries (or an hour), then judge. |
rollout.history | 20 | Versions kept for pinning back to (their files stay on the controller's volume). |
resolvers
| Key | Default | Meaning |
|---|---|---|
replicas | 2 | |
ephemeralTtlSeconds | 600 | Seconds the controller keeps a resolver pod it no longer hears from. |
resources.requests.cpu | 50m | |
resources.requests.memory | 64Mi | |
resources.limits.cpu | "1" | |
resources.limits.memory | 256Mi | |
podAnnotations | {} | |
nodeSelector | {} | |
tolerations | [] | |
affinity | {} | E.g. spread the pods: podAntiAffinity on app.kubernetes.io/component: resolver |
image
| Key | Default | Meaning |
|---|---|---|
repository | ghcr.io/paimonsoror/telltale | |
tag | "" | Default: the chart's appVersion. |
pullPolicy | IfNotPresent |
kubernetes
Kubernetes workloads as devices: the controller (or the allInOne pod) watches pods and namespaces, read-only, and names each pod address by its workload (media/deploy/jellyfin); a Deployment's replicas are one device across rollouts. Pods are seen as themselves only when they ask TelltaleDNS directly: point the cluster's DNS at the DNS Service (service.dns.clusterIP, then the kubelet's cluster DNS; see NOTES and docs/running.md), or give a workload dnsConfig.nameservers. TelltaleDNS sends the cluster domain (and the pod and service reverse zones) on to CoreDNS. Don't set [kubernetes] in config as well: the chart writes it.
| Key | Default | Meaning |
|---|---|---|
enabled | false | |
clusterDomain | cluster.local | The cluster's DNS domain. |
clusterDns | "" | CoreDNS's Service address (required when enabled): kubectl -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}' |
podCidr | "" | Optional: the pod and service networks (IPv4 CIDRs, e.g. 10.42.0.0/16 and 10.43.0.0/16 on k3s); their reverse zones (PTR) go to CoreDNS too, rounded to whole octets. |
serviceCidr | "" | |
namespaces | [] | Namespaces to watch (empty: all), and namespaces never watched. |
excludeNamespaces | [] | |
defaultGroup | "" | The group of a workload no label or rule places (empty: its network's group, or default). |
map | [] | Groups by namespace glob (and optionally kind and name), first match wins after the telltaledns.io/group labels: [{namespace: "media*", group: "iot"}]. |
rbac.create | true | A ServiceAccount, and a ClusterRole that can only get/list/watch pods and namespaces. |
rbac.serviceAccountName | "" | Use an existing ServiceAccount instead (it needs those rights). |
service
| Key | Default | Meaning |
|---|---|---|
dns.type | LoadBalancer | One Service with both UDP and TCP 53. |
dns.port | 53 | |
dns.clusterIP | "" | A fixed cluster address for the Service, so the kubelet's cluster DNS setting survives reinstalls (e.g. 10.43.0.53 inside k3s's 10.43.0.0/16). Empty: assigned. |
dns.externalTrafficPolicy | Local | Local keeps client source IPs (per-device filtering and the query log need them). Cluster masks them behind node IPs: NOTES.txt warns. |
dns.loadBalancerIP | "" | |
dns.loadBalancerClass | "" | |
dns.annotations | {} | E.g. metallb.universe.tf/loadBalancerIPs, io.cilium/lb-ipam-ips, kube-vip.io/loadbalancerIPs |
dns.alsoServe | One address for everything: the DNS Service also carries the UI/API port and (scaled) the cluster port. Only the controller has those ports, so only it gets that traffic. Use this instead of giving the api or cluster Service the DNS Service's IP: with externalTrafficPolicy Local, MetalLB and Cilium share an IP only between Services that select the same pods, and in mode: scaled they don't (the DNS Service would lose its IP). See docs/running.md, "Scaling in Kubernetes". | |
dns.alsoServe.api | false | |
dns.alsoServe.cluster | false | |
api.type | ClusterIP | The web UI and REST API. ClusterIP plus ingress, or LoadBalancer on the LAN. |
api.port | 8053 | |
api.externalTrafficPolicy | Local | |
api.loadBalancerIP | "" | |
api.annotations | {} | |
metrics.port | 9153 |
encrypted
Encrypted DNS for devices: DNS over TLS (853) and DNS over HTTPS (443) on the same DNS Service. Needs a certificate: an existing kubernetes.io/tls Secret, or one from cert-manager. A wildcard name (*.dns.example.com) lets devices identify themselves (kids-tablet.dns.example.com → client ID "kids-tablet"). The certificate is re-read when cert-manager renews it; no restart.
| Key | Default | Meaning |
|---|---|---|
dot.enabled | false | |
dot.port | 853 | |
doh.enabled | false | |
doh.port | 443 | |
doh.path | /dns-query | |
doh.http3 | false | Also serve DoH over HTTP/3 (QUIC, UDP on the same port); HTTP/2 answers advertise it with Alt-Svc. |
doq | DNS over QUIC (RFC 9250) on UDP 853: Android and iOS apps and some resolvers prefer it. Uses the same certificate. | |
doq.enabled | false | |
doq.port | 853 | |
proxyProtocol | false | Accept PROXY protocol v2 on TCP, DoT, and DoH (only when a balancer in front sends it: every connection must then start with the header). |
tls.secretName | "" | An existing Secret with tls.crt and tls.key. Ignored when certManager.enabled. |
tls.certManager.enabled | false | |
tls.certManager.issuerRef.name | "" | |
tls.certManager.issuerRef.kind | ClusterIssuer | |
tls.certManager.dnsNames | [] | E.g. [dns.example.com, "*.dns.example.com"] |
ingress
The UI and API behind an Ingress (TLS). Also set [api] trusted_proxies in config to the ingress's source network so sign-in lockouts and the audit log see client addresses.
| Key | Default | Meaning |
|---|---|---|
enabled | false | |
className | "" | |
host | "" | |
annotations | {} | E.g. with cert-manager: {cert-manager.io/cluster-issuer: letsencrypt} |
tls.secretName | "" |
persistence
| Key | Default | Meaning |
|---|---|---|
enabled | true | Filter snapshots, downloaded lists, the query log, rollups, users (state.db). |
size | 2Gi | |
storageClass | "" | |
accessModes | [ReadWriteOnce] | |
existingClaim | "" | Use this PersistentVolumeClaim instead of creating one (size, storageClass, and accessModes are then ignored): for moving an existing install's data to this chart. |
resources
| Key | Default | Meaning |
|---|---|---|
requests.cpu | 100m | |
requests.memory | 96Mi | |
limits.cpu | "2" | |
limits.memory | 512Mi | Steady state ~35 MiB with typical lists; compiling large lists peaks near 110 MiB. |
auth
| Key | Default | Meaning |
|---|---|---|
bootstrapAdmin | Create the first admin from a Secret instead of the setup token. Keys: username and password or password-hash (an Argon2id hash from telltale auth hash-password). | |
bootstrapAdmin.existingSecret | "" | |
bootstrapAdmin.usernameKey | username | |
bootstrapAdmin.passwordKey | password | |
bootstrapAdmin.passwordHashKey | password-hash |
serviceMonitor
| Key | Default | Meaning |
|---|---|---|
enabled | false | |
interval | 30s | |
scrapeTimeout | 10s | |
labels | {} |
prometheusRule
Default alerts: down, upstreams down, SERVFAIL rate, stale lists, dropped telemetry.
| Key | Default | Meaning |
|---|---|---|
enabled | false | |
labels | {} | |
slo | SLO recording rules (telltale:slo_*_errors:ratio_rate<window>) and multi-window burn-rate alerts over every node. Keep these equal to the nodes' [slo] settings, which the web UI and the API use; latencyMs must be a bucket bound of telltale_query_duration_seconds (1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000). | |
slo.enabled | true | |
slo.availabilityTarget | 99.9 | |
slo.latencyTarget | 99 | |
slo.latencyMs | 250 |
grafanaDashboard
The Grafana dashboard as a ConfigMap for the Grafana sidecar.
| Key | Default | Meaning |
|---|---|---|
enabled | false | |
labels.grafana_dashboard | "1" | |
annotations | {} |
networkPolicy
| Key | Default | Meaning |
|---|---|---|
enabled | false | |
dnsFrom | [] | CIDRs allowed to query DNS / reach the UI+API / scrape metrics. Empty: anyone. |
apiFrom | [] | |
metricsFrom | [] |
podSecurityContext
| Key | Default | Meaning |
|---|---|---|
fsGroup | 65532 | |
fsGroupChangePolicy | OnRootMismatch | |
seccompProfile.type | RuntimeDefault |
securityContext
| Key | Default | Meaning |
|---|---|---|
runAsUser | 65532 | |
runAsGroup | 65532 | |
runAsNonRoot | true | |
readOnlyRootFilesystem | true | |
allowPrivilegeEscalation | false | |
capabilities.drop | [ALL] |