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.

  1. The install's config file holds the basics: where to listen and which upstreams to ask. Leave it as it is.
  2. Add and change everything else on its page in the web UI: upstreams, lists, groups and schedules, devices, local names, quick rules, and alerts.
  3. Each change shows what it will do first. Changes are stored in state.db next to users and the audit log, and they're included in backups.

Each editor's Manage table says where every entry comes from:

BadgeMeaning
config fileDefined in your file. Edit makes an override; Remove hides it.
added hereMade in the UI or API. Remove deletes it.
overrides the fileThe UI's version replaces the file's entry of the same name. Revert to the file brings the file's back.
hiddenThe 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.

  1. Edit telltale.toml. Split it if you like: later files override earlier ones (telltale run -c base.toml -c site.toml).
  2. Check it: telltale config check base.toml site.toml reports every problem at once with its key path. --print shows the effective configuration.
  3. Apply it without a restart: systemctl reload telltale, or docker 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.

  1. Put your settings under config: in the Helm values. The chart writes them after its own (listeners, data directory, ports).
  2. 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.

  1. Built-in defaults. Every key is optional.
  2. Config files, in the order given. Tables merge key by key; lists such as [[listen]] replace the earlier one entirely.
  3. Environment variables TELLTALE_<SECTION>_<KEY>, such as TELLTALE_CACHE_MAX_TTL=3600. Handy in containers.
  4. Command-line flags.
  5. 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.
  6. 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.

Shared or per node

In a cluster, the primary shares most settings with every node, including what was added in its UI. A few sections describe the machine itself, so each node keeps its own:

Shared by the cluster

Upstreams and routes, local names and zones, lists, groups and schedules, devices, quick rules, access and rate limits, special names, what's kept out of the log, change simulation, alerts, and the staged-rollout settings. Users and API tokens are the primary's too.

Each node's own

[node], [[listen]], [cluster], [api], [auth], [telemetry], and [cache], from that node's file. A [[record]] with node_only = true stays on its node too.

Change shared settings in one place. Use any node's UI or API: a change made on another node goes to the primary, which applies it and sends it to every node within seconds. For files, edit the primary's.

Set per-node sections on every node. A [cache] change, for example, has to go into each node's own file.

If a node's own file still sets shared sections, the primary's replace them, and the Cluster page's node_settings check says so until they're removed.

Where each setting lives

The same setting, three ways. API paths take PUT to save, DELETE to remove, and ?dryRun=true to preview.

SettingWeb UIConfig fileAPI
Upstream servers and groupsUpstreams[[upstream]], [[upstream_group]]/api/v1/upstreams/{name}, /api/v1/upstream-groups/{name}
BlocklistsLists[[list]]/api/v1/lists/{name}
GroupsGroups[[group]]/api/v1/groups/{name}
SchedulesGroups → Schedules[[schedule]]/api/v1/schedules/{name}
DevicesClick any address → Name this device…[[client]]/api/v1/clients/{name}
Local namesNames on my network[[record]], [[zone]]/api/v1/records/{name}
A domain sent to another serverNames on my network → Send a domain to another server[[route]]/api/v1/forwards/{domain}
Quick block and allow rulesQuick rules[[rule]]/api/v1/rules/{id}
Rate limitSettings → Query handling[ratelimit]/api/v1/ratelimit/default
Kept out of the logSettings → Query handling[exclusions]/api/v1/exclusions/default
Change simulationSettings → Query handling[simulate]/api/v1/simulate-settings/default
AlertsAlerts[[alerts.destination]], [[alerts.rule]]/api/v1/alerts/destinations/{name}, /api/v1/alerts/rules/{name}
Staged rolloutsSettings → 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.