Incus Source¶
The Incus source creates DNS A records for running Incus system containers and virtual machines. It polls the Incus REST API to discover instances, resolves each instance's IP address from its network state, then registers an A record mapping <instance-name>.<domain> to that IP.
It connects either to a local Unix socket (agent running on the Incus host) or to a remote HTTPS endpoint secured with a client certificate.
How It Works¶
flowchart LR
A["Incus API<br/>/1.0/instances?recursion=2"] -->|"Poll interval"| B["List containers / VMs"]
B -->|"Filter: state"| C["Resolve IP"]
C -->|"InstanceState.Network<br/>(first global inet addr)"| D["Hostname + IP"]
D --> E["Incus Source"]
E -->|"A record"| F["Reconciler → DNS"]
- Lister polls
/1.0/instances?recursion=2and applies the state filter - IP resolver scans each instance's network interfaces, skipping loopback and non-routable addresses, and selects the first global IPv4 address
- Source maps the instance name + configured domain suffix to a fully-qualified hostname
- Reconciler creates or updates A records via the matching DNS provider
Configuration¶
Environment Variables¶
| Variable | Required | Default | Description |
|---|---|---|---|
DNSWEAVER_INCUS_URL |
Alt | — | Remote Incus API base URL, e.g. https://incus-host:8443. Mutually exclusive with DNSWEAVER_INCUS_SOCKET_PATH. |
DNSWEAVER_INCUS_SOCKET_PATH |
Alt | — | Path to the local Incus Unix socket, e.g. /var/lib/incus/unix.socket. Mutually exclusive with DNSWEAVER_INCUS_URL. |
DNSWEAVER_INCUS_PROJECT |
No | (all / default) | Restrict discovery to a single Incus project |
DNSWEAVER_INCUS_STATE_FILTER |
No | running |
Instance status filter (running, stopped, etc.) |
DNSWEAVER_INCUS_DOMAIN_SUFFIX |
No | — | Domain suffix appended to instance names, e.g. home.example.com |
DNSWEAVER_INCUS_TARGET_MODE |
No | guest-ip |
Target resolution mode. guest-ip (default) emits an A record per instance IP. instance defers record type and target to the matching provider instance — useful for pointing all instances at a reverse proxy via CNAME. |
DNSWEAVER_INCUS_TLS_CA_FILE |
No | — | Path to PEM CA bundle that issued the Incus server certificate (remote HTTPS). |
DNSWEAVER_INCUS_TLS_CERT_FILE |
No | — | Client certificate for mutual TLS against the Incus API (pair with TLS_KEY_FILE). |
DNSWEAVER_INCUS_TLS_KEY_FILE |
No | — | Client private key for mutual TLS. |
DNSWEAVER_INCUS_TLS_SERVER_NAME |
No | — | SNI / verification hostname override. |
DNSWEAVER_INCUS_TLS_MIN_VERSION |
No | 1.2 |
Minimum TLS protocol version (1.2 or 1.3). |
DNSWEAVER_INCUS_TLS_SKIP_VERIFY |
No | false |
Skip Incus TLS certificate verification. Prefer TLS_CA_FILE. |
Pick exactly one endpoint
Set either DNSWEAVER_INCUS_URL (remote HTTPS) or
DNSWEAVER_INCUS_SOCKET_PATH (local socket) — never both. Setting both is a
configuration error and dnsweaver will refuse to start.
Source Registration¶
Add incus to DNSWEAVER_SOURCES:
Auto-registration
When DNSWEAVER_INCUS_URL or DNSWEAVER_INCUS_SOCKET_PATH is set, the Incus
source is automatically registered even if not listed in
DNSWEAVER_SOURCES. You only need to list it explicitly if you want to
control source ordering relative to other sources.
Hostname Resolution¶
The source determines the DNS hostname for each instance using this logic, in order of precedence:
user.dnsweaver.hostnameconfig key — set on the instance, its value is used verbatim as the hostname (e.g.incus config set web user.dnsweaver.hostname=app.example.net)- Instance name contains a dot — used directly as an FQDN (e.g.,
db.home.example.com) - Domain suffix configured — appended to the instance name (e.g.,
web+home.example.com→web.home.example.com) - None of the above — the instance is skipped; a debug log entry is emitted
Domain suffix is strongly recommended
Without a domain suffix, only instances whose names already contain a dot (or
that set user.dnsweaver.hostname) will produce DNS records. Set
DNSWEAVER_INCUS_DOMAIN_SUFFIX to ensure all instances are registered.
Per-instance hostname override¶
Pin an arbitrary FQDN to any instance with a config key:
This takes precedence over the derived <name>.<domain> hostname.
IP Address Resolution¶
The resolver reads each instance's live network state (InstanceState.Network) and selects an address by:
- Sorting interface names for deterministic selection
- Skipping loopback interfaces (
lo,lo0) - Selecting the first global IPv4 (
inet) address - Skipping non-routable addresses (link-local, etc.)
Tailscale / CGNAT addresses are kept
Addresses in the 100.64.0.0/10 CGNAT range (used by Tailscale) are treated
as valid targets, so Tailscale-connected instances resolve to their tailnet IP.
Instances with no resolvable IP are skipped in both target modes — the IP existence acts as a liveness gate.
Target Mode¶
DNSWEAVER_INCUS_TARGET_MODE controls what the source emits for each discovered instance:
| Mode | Record Type | Target | Use Case |
|---|---|---|---|
guest-ip (default) |
A |
Instance's resolved IP | Direct DNS resolution to each container/VM |
instance |
from instance | from instance | Point all Incus-discovered hostnames at a reverse proxy |
In instance mode, the source emits the hostname only (no record-type or target hints).
The matching provider instance's RECORD_TYPE and TARGET drive the resulting record,
so a CNAME instance pointed at NPMplus / Traefik / Caddy will create CNAME records for
every Incus instance that matches its DOMAINS filter.
Example: CNAME everything to a reverse proxy¶
DNSWEAVER_SOURCES=incus
DNSWEAVER_INCUS_SOCKET_PATH=/var/lib/incus/unix.socket
DNSWEAVER_INCUS_DOMAIN_SUFFIX=home.example.com
DNSWEAVER_INCUS_TARGET_MODE=instance # opt in
DNSWEAVER_INSTANCES=npmplus
DNSWEAVER_NPMPLUS_TYPE=technitium
DNSWEAVER_NPMPLUS_RECORD_TYPE=CNAME
DNSWEAVER_NPMPLUS_TARGET=npmplus.home.example.com # all instances point here
DNSWEAVER_NPMPLUS_DOMAINS=*.home.example.com
DNSWEAVER_NPMPLUS_URL=https://technitium.home.example.com
DNSWEAVER_NPMPLUS_TOKEN_FILE=/run/secrets/technitium_token
Every Incus instance matching *.home.example.com will get a CNAME pointing to
npmplus.home.example.com instead of an A record pointing at the instance's own IP.
Remote HTTPS Access¶
To reach Incus over the network, add a trust certificate to the Incus server and point dnsweaver at the API with a matching client certificate:
# On the Incus host: expose the API and trust dnsweaver's client cert
incus config set core.https_address :8443
incus config trust add-certificate dnsweaver.crt
Then configure dnsweaver:
DNSWEAVER_INCUS_URL=https://incus-host.home.example.com:8443
DNSWEAVER_INCUS_TLS_CERT_FILE=/run/secrets/incus_client_cert
DNSWEAVER_INCUS_TLS_KEY_FILE=/run/secrets/incus_client_key
DNSWEAVER_INCUS_TLS_CA_FILE=/run/secrets/incus_server_ca
Local socket needs no TLS
When using DNSWEAVER_INCUS_SOCKET_PATH, no TLS configuration is required —
access is governed by Unix socket file permissions. Mount the socket into the
container and ensure the dnsweaver process can read it.
Workload Metadata¶
Each Incus instance is mapped to a workload with the following metadata:
| Field | Value |
|---|---|
Platform |
incus |
Kind |
incus-container or incus-vm |
ID / Router |
<project>/<instance-name> |
| Labels | The instance's config keys, verbatim (e.g. user.dnsweaver.hostname) |
Example: Docker Compose (local socket)¶
services:
dnsweaver:
image: ghcr.io/maxfield-allison/dnsweaver:latest
environment:
DNSWEAVER_SOURCES: incus
DNSWEAVER_INCUS_SOCKET_PATH: /var/lib/incus/unix.socket
DNSWEAVER_INCUS_DOMAIN_SUFFIX: home.example.com
volumes:
# Mount the host's Incus socket read-only
- /var/lib/incus/unix.socket:/var/lib/incus/unix.socket:ro
Example: Docker Compose (remote HTTPS)¶
services:
dnsweaver:
image: ghcr.io/maxfield-allison/dnsweaver:latest
environment:
DNSWEAVER_SOURCES: incus
DNSWEAVER_INCUS_URL: https://incus-host.home.example.com:8443
DNSWEAVER_INCUS_TLS_CERT_FILE: /run/secrets/incus_client_cert
DNSWEAVER_INCUS_TLS_KEY_FILE: /run/secrets/incus_client_key
DNSWEAVER_INCUS_TLS_CA_FILE: /run/secrets/incus_server_ca
DNSWEAVER_INCUS_DOMAIN_SUFFIX: home.example.com
secrets:
- incus_client_cert
- incus_client_key
- incus_server_ca
secrets:
incus_client_cert:
file: ./secrets/incus_client.crt
incus_client_key:
file: ./secrets/incus_client.key
incus_server_ca:
file: ./secrets/incus_server_ca.pem
Example: Kubernetes Secret (remote HTTPS)¶
apiVersion: v1
kind: Secret
metadata:
name: dnsweaver-incus
namespace: dnsweaver
type: Opaque
stringData:
client.crt: |
-----BEGIN CERTIFICATE-----
...
client.key: |
-----BEGIN PRIVATE KEY-----
...
server-ca.pem: |
-----BEGIN CERTIFICATE-----
...
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dnsweaver
namespace: dnsweaver
spec:
template:
spec:
containers:
- name: dnsweaver
env:
- name: DNSWEAVER_SOURCES
value: incus
- name: DNSWEAVER_INCUS_URL
value: https://incus-host.home.example.com:8443
- name: DNSWEAVER_INCUS_TLS_CERT_FILE
value: /etc/dnsweaver/incus/client.crt
- name: DNSWEAVER_INCUS_TLS_KEY_FILE
value: /etc/dnsweaver/incus/client.key
- name: DNSWEAVER_INCUS_TLS_CA_FILE
value: /etc/dnsweaver/incus/server-ca.pem
- name: DNSWEAVER_INCUS_DOMAIN_SUFFIX
value: home.example.com
volumeMounts:
- name: incus-tls
mountPath: /etc/dnsweaver/incus
readOnly: true
volumes:
- name: incus-tls
secret:
secretName: dnsweaver-incus