Guide
Choose how you manage settings
You can change TelltaleDNS from the web UI, a config file, Git, or the API. Pick the style that fits how you run it; they work together, and your config file is never rewritten.
Four ways to change a setting
Web UI
Upstreams, lists, groups, devices, local names, alerts, and more have a page with an editor. Check previews a change and warns you; nothing is saved until Apply.
Config file
telltale.toml, plus more files if you like. Check it with telltale config check, then apply it with a reload. Every key is in the configuration reference.
Git
Keep the file in a repository: Helm values with Argo CD or Flux, or a cluster that reads its shared settings straight from a repository.
API, CLI, and AI agents
Everything the UI changes, the API changes too (?dryRun=true previews). telltale ctl wraps it for the shell, and AI agents propose plans that a person approves.
Pick a style
Most people use the first one. The others suit infrastructure that's managed as code.
Web UI on a config file (the default)
Good for: a Raspberry Pi or a home server you look after in the browser.
- The install's config file holds the basics: where to listen and which upstreams to ask. Leave it as it is.
- Add and change everything else on its page in the web UI: upstreams, lists, groups and schedules, devices, local names, quick rules, and alerts.
- Each change shows what it will do first. Changes are stored in
state.dbnext to users and the audit log, and they're included in backups.
Each editor's Manage table says where every entry comes from:
| Badge | Meaning |
|---|---|
| config file | Defined in your file. Edit makes an override; Remove hides it. |
| added here | Made in the UI or API. Remove deletes it. |
| overrides the file | The UI's version replaces the file's entry of the same name. Revert to the file brings the file's back. |
| hidden | The file's entry is left out. Revert to the file brings it back. |
Config file only
Good for: Ansible, Puppet, or a dotfiles repository, where every change is a reviewed diff.
- Edit
telltale.toml. Split it if you like: later files override earlier ones (telltale run -c base.toml -c site.toml). - Check it:
telltale config check base.toml site.tomlreports every problem at once with its key path.--printshows the effective configuration. - Apply it without a restart:
systemctl reload telltale, ordocker kill -s HUP telltale. A file that doesn't validate is refused and the previous configuration keeps serving.
telltale config check /etc/telltale/telltale.toml # "config OK", or every error with its path
sudo systemctl reload telltale # applied with no dropped queries
Upstreams, groups, routes, local names, access, rate limits, special names, and listeners apply at once. [cache], [telemetry], and [node] wait for a restart (the log names them).
To keep the file the only source, give everyone else the viewer role: viewers see everything but change nothing.
Git, per node (Helm, Argo CD, Flux)
Good for: Kubernetes, where Argo CD or Flux applies the Helm values from your repository.
- Put your settings under
config:in the Helm values. The chart writes them after its own (listeners, data directory, ports). - Tell TelltaleDNS the file comes from Git, so the UI can help you keep the repository in step:
config: |
[cluster]
config_source = "gitops"
[[upstream]]
name = "quad9"
url = "tls://9.9.9.9"
tls_server_name = "dns.quad9.net"
Changes in the UI still work, at once. After Apply, the UI shows the TOML to add to your repository (under config:) with a Copy button. Once Git has it, Revert to the file removes the UI's copy, so the next rebuild from Git doesn't lose anything. The API returns the same text as keepInGit.
The Kubernetes guide covers the rest of the chart.
Git, for the whole cluster
Good for: "nothing changes unless it's in Git", across every node.
Option 1: the cluster reads one file from a repository. The primary fetches the shared settings, checks them, and hands them to every node, so every node serves the same commit:
# On every node that may become primary:
[cluster.git]
repo = "https://github.com/me/homelab"
ref = "main"
path = "telltale/shared.toml"
# require_signed = true # only SSH-signed commits...
# allowed_signers_file = "/etc/telltale/allowed_signers"
# webhook_secret_file = "/etc/telltale/git-hook" # push webhook: applied in seconds
- A commit is used only if it validates and descends from the one in use; anything else is refused, and the cluster stays on the last good commit.
- If the repository is unreachable, every node keeps serving the last commit. DNS never waits for Git.
Option 2: only Git-managed nodes may publish. On the primary: telltale cluster set-authority gitops (or telltale cluster init --config-authority gitops), then restart. Only nodes with [cluster] config_source = "gitops" may publish the cluster's configuration. Any other node can only take over as an emergency primary, which keeps the last version until a Git-managed node is back.
In both cases the UI and API stop changing shared settings: they answer 409 gitops_managed, and every change goes through your repository.
How the layers stack
From lowest to highest; a higher layer wins.
- Built-in defaults. Every key is optional.
- Config files, in the order given. Tables merge key by key; lists such as
[[listen]]replace the earlier one entirely. - Environment variables
TELLTALE_<SECTION>_<KEY>, such asTELLTALE_CACHE_MAX_TTL=3600. Handy in containers. - Command-line flags.
- The web UI and API, entry by entry: they add an upstream, list, or group, or override or hide one of the file's by name.
- In a cluster, the primary. Its shared settings replace the shared sections of every other node's own files.
telltale config check telltale.toml --print shows layers 1 to 4 merged. The UI's entries are listed with their source by GET /api/v1/config/entries.
Where each setting lives
The same setting, three ways. API paths take PUT to save, DELETE to remove, and ?dryRun=true to preview.
| Setting | Web UI | Config file | API |
|---|---|---|---|
| Upstream servers and groups | Upstreams | [[upstream]], [[upstream_group]] | /api/v1/upstreams/{name}, /api/v1/upstream-groups/{name} |
| Blocklists | Lists | [[list]] | /api/v1/lists/{name} |
| Groups | Groups | [[group]] | /api/v1/groups/{name} |
| Schedules | Groups → Schedules | [[schedule]] | /api/v1/schedules/{name} |
| Devices | Click any address → Name this device… | [[client]] | /api/v1/clients/{name} |
| Local names | Names on my network | [[record]], [[zone]] | /api/v1/records/{name} |
| A domain sent to another server | Names on my network → Send a domain to another server | [[route]] | /api/v1/forwards/{domain} |
| Quick block and allow rules | Quick rules | [[rule]] | /api/v1/rules/{id} |
| Rate limit | Settings → Query handling | [ratelimit] | /api/v1/ratelimit/default |
| Kept out of the log | Settings → Query handling | [exclusions] | /api/v1/exclusions/default |
| Change simulation | Settings → Query handling | [simulate] | /api/v1/simulate-settings/default |
| Alerts | Alerts | [[alerts.destination]], [[alerts.rule]] | /api/v1/alerts/destinations/{name}, /api/v1/alerts/rules/{name} |
| Staged rollouts | Settings → Cluster | [cluster.rollout] | /api/v1/cluster/rollout-settings/default |
| Listeners, the API, sign-in, telemetry, cache, the node | (config file only) | [[listen]], [api], [auth], [telemetry], [cache], [node] | (config file only) |
Every change through the UI or API is checked first, refused if it would leave the configuration invalid, and recorded in the audit log with who made it. The full API is described at /api/v1/openapi.json on every node.