DNS Configuration
Defines DNS zones and records managed as code. YAML files declare zones, record sets, and TTL
values — validated by strata and built into dns.auto.tfvars.json for Terraform consumption.
When to Use
Use the dns kind when you want to:
Manage DNS zones and records as code alongside your infrastructure
Validate record structure before applying to a provider (INWX, Cloudflare, Route53, etc.)
Keep DNS configuration in the same YAML workflow as firewalls and namespaces
Schema
apiVersion: strata.huybrechts.xyz/v1
kind: dns
meta:
name: <resource_name> # Required: ^[a-z][a-z0-9_]*$
annotations:
description: <description>
labels:
version: "<version>"
spec:
provider: <provider> # Optional: inwx, cloudflare, route53, etc.
references: # Optional: declare keys used by var/secret records
variables:
- <var_key> # Resolved from environment variables at build time
secrets:
- <secret_key> # Injected as TF_VAR_<key> at deploy time
zones:
- name: <zone> # Required: fully-qualified domain name
ttl: <seconds> # Optional: default TTL for all records in this zone
records:
- name: <record> # Required: hostname or "@" for the zone apex
type: <type> # Required: A, AAAA, CNAME, MX, TXT, SRV, NS, PTR, CAA
value: <value> # one of: literal record value
var: <key> # one of: variable key from spec.references.variables
secret: <key> # one of: secret key from spec.references.secrets
ttl: <seconds> # Optional: per-record override of the zone TTL
priority: <n> # Optional: MX and SRV records only
Top-level Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No |
DNS provider hint ( |
|
object |
No |
Variable and secret key declarations. Required when any record uses |
|
array |
Yes |
One or more zone definitions. |
spec.references Fields
Declares the variable and secret keys that records in this file may reference. Required when
any record uses var: or secret:.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
list of strings |
No |
Keys resolved from environment variables at build time. |
|
list of strings |
No |
Keys injected as |
Zone Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Fully-qualified domain name of the zone. |
|
int |
No |
Default TTL (seconds) for all records. |
|
array |
No |
List of DNS records for this zone. |
Record Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Hostname, subdomain, or |
|
string |
Yes |
Record type — see table below. |
|
string |
one of |
Literal record value. Written directly to |
|
string |
one of |
Variable key from |
|
string |
one of |
Secret key from |
|
int |
No |
Per-record TTL. Overrides zone-level |
|
int |
No |
Required for MX and SRV; invalid on all other types. |
Exactly one of
value,var, orsecretmust be set per record.
Record Type Reference
Type |
Value format |
Notes |
|---|---|---|
|
IPv4 address ( |
|
|
IPv6 address ( |
|
|
Fully-qualified target with trailing dot ( |
Cannot be used at the zone apex ( |
|
Fully-qualified mail server with trailing dot |
Requires |
|
Quoted string ( |
SPF, DKIM, DMARC, and domain-verification records. |
|
|
Requires |
|
Fully-qualified nameserver with trailing dot |
Delegation records. Usually pre-set by your registrar. |
|
Fully-qualified reverse target with trailing dot |
Reverse-DNS; use inside |
|
|
Restricts which CAs may issue certificates. |
Trailing dot: CNAME, MX, NS, PTR, and SRV values must end with . to indicate a
fully-qualified name. Omitting it may cause the provider to append the zone name automatically.
Example
A complete zone for a production domain with the most common record types:
apiVersion: strata.huybrechts.xyz/v1
kind: dns
meta:
name: haven_zones
annotations:
description: Haven DNS zones — managed via INWX
labels:
owner: vincent
version: "1.0.0"
spec:
provider: inwx
references:
variables:
- extra_spf_include # resolved at build time from environment.yaml
secrets:
- google_verify_token # injected at deploy time via TF_VAR_google_verify_token
zones:
- name: huybrechts.xyz
ttl: 3600
records:
# Apex A record — points the root domain to the server
- name: "@"
type: A
value: "1.2.3.4"
# WWW subdomain
- name: www
type: CNAME
value: "huybrechts.xyz."
# Mail routing (Proton Mail — higher priority = preferred)
- name: "@"
type: MX
value: "mail.protonmail.ch."
priority: 10
- name: "@"
type: MX
value: "mailsec.protonmail.ch."
priority: 20
# SPF — value sourced from extra_spf_include variable at build time
- name: "@"
type: TXT
var: extra_spf_include
# Google domain verification — secret injected at deploy time
- name: "@"
type: TXT
secret: google_verify_token
# DMARC — quarantine messages failing SPF/DKIM
- name: _dmarc
type: TXT
value: "v=DMARC1; p=quarantine; rua=mailto:dmarc@huybrechts.xyz"
# CAA — only Let's Encrypt may issue certificates
- name: "@"
type: CAA
value: "0 issue \"letsencrypt.org\""
Variable and secret records
Records support three mutually exclusive value sources. Choose based on how sensitive and environment-specific the record value is.
Source |
Use when |
Build behaviour |
|---|---|---|
|
The record value is the same in every environment |
Written literally to |
|
The value varies by environment but is not sensitive |
Resolved from |
|
The value is sensitive (API key, verification token, etc.) |
Not written to |
var: — build-time variable resolution
When a record uses var: <key>, strata resolves <key> from the spec.variables section of
your active environment.yaml at strata build run and writes the resolved value into
dns.auto.tfvars.json. Declare the key in both places:
spec.references.variablesin the DNS YAML filespec.variablesin the matchingenvironment.yaml
secret: — deploy-time injection
When a record uses secret: <key>, the record is emitted into a separate Terraform variable
(dns_secret_records) and is not written to dns.auto.tfvars.json. The value is never
stored on disk by strata. Instead, declare the secret in environment.yaml under spec.secrets
and ensure it is available as TF_VAR_<key> when Terraform runs.
Your Terraform DNS module must declare a dedicated variable for secret records:
variable "dns_secret_records" {
type = map(string)
sensitive = true
}
Declare the key in both places:
spec.references.secretsin the DNS YAML filespec.secretsin the matchingenvironment.yaml
Linking to a Workspace
Reference one or more DNS zone files from your workspace YAML using spec.dns_zones:
# workspace.yaml
spec:
dns_zones:
- name: haven_zones
file: "@haven-config/dns/haven-zones.yaml"
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Unique name. Other workspace files reference this name. |
|
string |
Yes |
Path to the DNS YAML file. Supports |
Names must be unique across all dns_zones entries in the workspace.
Build Output
Running strata build run generates the following files in the build directory:
.strata/build/<deployment>/
terraform/
dns.auto.tfvars.json ← Terraform: dns_zones variable
dns_secret_records.auto.tfvars.json ← Terraform: secret record stubs (only when secrets present)
ansible/
strata_dns.yml ← Ansible: strata_dns_zones variable
Terraform — dns.auto.tfvars.json
Contains one top-level variable dns_zones — a map keyed by the DNS config name. Your
Terraform DNS module declares variable "dns_zones" {} and loads it automatically via the
*.auto.tfvars.json convention.
{
"dns_zones": {
"haven_zones": {
"description": "Haven DNS zones — managed via INWX",
"labels": {"owner": "vincent"},
"tags": [],
"provider": "inwx",
"zones": {
"huybrechts.xyz": {
"ttl": 3600,
"records": [
{"name": "@", "type": "A", "value": "1.2.3.4", "ttl": null, "priority": null},
{"name": "www", "type": "CNAME", "value": "huybrechts.xyz.", "ttl": null, "priority": null},
{"name": "@", "type": "MX", "value": "mail.protonmail.ch.", "ttl": null, "priority": 10},
{"name": "@", "type": "TXT", "value": "v=spf1 include:...", "ttl": null, "priority": null},
{"name": "@", "type": "TXT", "value": null, "ttl": null, "priority": null}
]
}
}
}
}
}
value:records are written literally.var:records are resolved fromenvironment.yamlat build time and written as the resolved string.secret:records are emitted with"value": null— the actual value is never written to disk.
Terraform — dns_secret_records.auto.tfvars.json
Only written when at least one record uses secret:. Contains record stubs keyed by
{record_name}_{record_type} so your Terraform module can look up the secret value at apply
time via TF_VAR_<secret_key>.
{
"dns_secret_records": {
"haven_zones": {
"huybrechts.xyz": {
"@_TXT": {
"name": "@",
"type": "TXT",
"secret_key": "google_verify_token",
"ttl": null,
"priority": null
}
}
}
}
}
Declare the matching variable in your Terraform module and mark it sensitive:
variable "dns_secret_records" {
type = any
sensitive = true
default = {}
}
At deploy time, set TF_VAR_google_verify_token=<value> so Terraform can resolve it.
Ansible — strata_dns.yml
Written only when DNS zones are present. Contains one top-level variable strata_dns_zones
with the same map structure as Terraform. Load it with vars_files or place it in your
group_vars/ directory.
strata_dns_zones:
haven_zones:
description: "Haven DNS zones — managed via INWX"
labels:
owner: vincent
tags: []
provider: inwx
zones:
huybrechts.xyz:
ttl: 3600
records:
- {name: "@", type: A, value: "1.2.3.4", ttl: null, priority: null}
- {name: www, type: CNAME, value: "huybrechts.xyz.", ttl: null, priority: null}
- {name: "@", type: MX, value: "mail.protonmail.ch.", ttl: null, priority: 10}
- {name: "@", type: TXT, value: "v=spf1 include:...", ttl: null, priority: null}
Note: Only
value:records are emitted in the Ansible output. Records that usevar:orsecret:are omitted — Ansible does not perform secret injection. If you need sensitive records via Ansible, source them from Vault or another secrets backend at playbook runtime and merge them into the zone structure yourself.
Reference the variable in a playbook task:
- name: Ensure DNS records
community.general.inwx_record:
domain: "{{ item.0.key }}"
record: "{{ item.1.name }}"
type: "{{ item.1.type }}"
value: "{{ item.1.value }}"
ttl: "{{ item.1.ttl | default(3600) }}"
loop: >-
{{ strata_dns_zones[dns_config].zones | dict2items
| subelements('value.records') }}
vars:
dns_config: haven_zones
Validation Rules
Rule |
Enforcement |
|---|---|
|
Pydantic / model load |
Each |
Workspace validator |
|
Model validator |
|
Pydantic / model load |
CNAME must not be used at the zone apex ( |
Model validator |
Exactly one of |
Model validator |
Any key used in |
Model validator |
Best Practices
Use trailing dots on CNAME, MX, NS, PTR, and SRV values — always fully qualify targets.
One file per domain group keeps diffs readable; group logically related zones when you manage many small delegated subdomains in a single file.
Set zone-level TTL as a baseline, then override per record only where the caching requirement genuinely differs (e.g. low TTL for a record you update frequently).
Comment records with their purpose (SPF selector, DKIM key id, etc.) using YAML inline comments — strata does not carry comments into the build output, but they stay in source for reviewers.
Naming: Lowercase with underscores (
haven_zones,api_domains).