incus-compose provides automatic, real-time DNS resolution for your compose
fleet through the ic-dns sidecar. Sourced directly from the Incus API and kept
synchronized by its event stream, it delivers split-horizon DNS with one zone
per project — without manual zone files or server reloads.
Enabled by default. When you run
incus-compose up, the tool automatically brings upic-dns, assigns the project zone (<project>.incus), and setsoci.dns.nameserversandoci.dns.searchon every instance. All services and containers can resolve each other out of the box.
flowchart TD
subgraph Incus["Incus Host"]
API["Incus API & Lifecycle Events"]
end
subgraph Sidecar["DNS Sidecar (ic-dns)"]
ENRICHER["ievent Enricher / Cache"]
DNS["CoreDNS Server (:53)"]
ENRICHER -->|"real-time zone updates"| DNS
end
subgraph Project["Compose Project Network"]
CLIENT["client instance"]
WEB["web instance (10.90.190.10)"]
end
API -->|"lifecycle event stream"| ENRICHER
CLIENT -->|"1. DNS query: web.shop.incus"| DNS
DNS -->|"2. Answer: 10.90.190.10"| CLIENT
CLIENT -.->|"3. Direct connection"| WEB
incus-compose dns *)Manage the ic-dns sidecar directly using the incus-compose dns command
group:
| Command | Description |
|---|---|
incus-compose dns status |
Print the status, IP addresses, readiness, and metrics of ic-dns |
incus-compose dns logs |
Stream or inspect the ic-dns sidecar container logs |
incus-compose dns up |
Create or recreate the ic-dns sidecar container |
incus-compose dns down |
Stop and remove the ic-dns sidecar container |
Check if the DNS sidecar is running and ready:
incus-compose dns status
Status: ready
IPv4: 10.90.190.53
IPv6: fd42::53
Include Prometheus metrics in the status report:
incus-compose dns status --metrics
Stream live queries and events from the daemon:
incus-compose dns logs --follow
Explicitly spin up or recreate the daemon (e.g. after updating an image or config):
incus-compose dns up
Recreate the daemon with trace-level logging:
incus-compose --trace dns up
Stop and tear down the sidecar:
incus-compose dns down
Opt out of DNS during stack deployment:
incus-compose up --no-dns
x-incus-compose.dns)Configure DNS behavior directly in your compose.yaml using the top-level
x-incus-compose.dns extension:
x-incus-compose:
dns:
disabled: false
scope: global
zone: shop.example.org
network: custom-net
ipv4_address: 10.0.0.2
ipv6_address: fd42::2
no_metrics: false
allow_transfer:
- 192.168.1.0/24
- 10.0.0.53/32
forwarders:
- 1.1.1.1
- 8.8.8.8
services:
web:
image: docker.io/nginx:alpine
networks:
default:
aliases:
- api.shop.example.org
client:
image: docker.io/busybox:glibc
command: ["sleep", "infinity"]
| Key | Type | Default | Description |
|---|---|---|---|
disabled |
boolean | false |
Disable DNS sidecar creation and DNS configuration for the project. Equivalent to --no-dns on incus-compose up. |
scope |
string | global |
Sidecar lifecycle scope: global (one shared daemon in the incus-compose project) or project (a dedicated sidecar scoped to this project). |
zone |
string | <project>.incus |
Custom DNS zone domain for the project, replacing the default <project>.<suffix>. |
network |
string | project network | Network attachment for the DNS sidecar. |
ipv4_address |
string | auto | Static IPv4 address assigned to the DNS sidecar instance. |
ipv6_address |
string | auto | Static IPv6 address assigned to the DNS sidecar instance. |
no_metrics |
boolean | false |
Disable the HTTP Prometheus /metrics endpoint on the sidecar. |
allow_transfer |
list of strings | [] |
CIDR prefix(es) permitted to perform zone transfers (AXFR/IXFR). Configures --allow-transfer on the sidecar and automatically stamps user.label.dns.transfer=true on the project. |
transfer |
boolean | false |
Explicitly opt the project zone into transfers (user.label.dns.transfer=true) without configuring sidecar CIDRs (useful when sharing a global sidecar). |
forwarders |
list of strings | /etc/resolv.conf |
Upstream DNS server(s) (ip, ip:port, or resolv.conf path) for external domains. Defaults to the sidecar's /etc/resolv.conf when omitted or empty. |
Warning: On an OVN-capable Incus server, the shared
globalic-dnssidecar runs on the OVNicompose0network and reaches project networks via OVN network peering. Instances onbridgenetworks (such as custom bridges withx-incus-compose.network.driver: bridgeor external bridges likeincusbr0) cannot reachicompose0, so mixing the global OVNic-dnswith bridge networks leaves DNS unreachable. Bridge projects on an OVN server must either run a dedicated sidecar (scope: project, plusnetwork:when not usingdefault) or disableic-dns(disabled: true/--no-dns) to use the bridge's built-indnsmasq.
| Name | When | Description |
|---|---|---|
<instance>.<zone> |
always | Individual instance name |
<service>.<zone> |
user.label.incus-compose.service, else user.label.dns.service |
Resolves to all replicas of that service |
<alias>.<zone>, or any absolute name |
user.label.dns.aliases, or compose aliases |
CNAME pointing to <instance>.<zone> |
in-addr.arpa / ip6.arpa |
every address inside its own network's prefixes | Reverse lookup returning the instance FQDN |
<zone> defaults to <project>.<suffix>, where --suffix is incus unless
changed.
A service name resolves to every replica carrying it, enabling round-robin load distribution across scaled instances. A PTR record always resolves to the unique instance name rather than the service name.
An Incus fleet is not one flat network. A project has its own bridges or OVN networks, an instance sits on some of them, and two instances that share no network cannot reach each other. A single static answer for a name would either hand out an unreachable IP address or leak the existence of hosts across project boundaries.
ic-dns returns NXDOMAIN rather than NODATA
or timing out. Response codes leak nothing about other existing
infrastructure.sequenceDiagram
participant ClientA as Client A (shares net1)
participant ClientB as Client B (on net2)
participant DNS as ic-dns
participant Web as Web (on net1)
Note over ClientA,Web: Client A shares net1 with Web
ClientA->>DNS: Query A web.shop.incus
DNS-->>ClientA: Answer: 10.90.1.10 (routable)
ClientA->>Web: Connect to 10.90.1.10
Note over ClientB,Web: Client B shares no network with Web
ClientB->>DNS: Query A web.shop.incus
DNS-->>ClientB: NXDOMAIN (fail-closed, no leak)
add-subnet=32,128.resolv.conf targets ic-dns directly).DNS behavior can also be controlled at the Incus project and instance level
using user.label.dns.* keys:
| Key | Set on | Meaning |
|---|---|---|
scope |
project | Opts the project in (global or project) |
zone |
project | The full zone name, replacing <project>.<suffix> |
ns |
project | Comma-separated NS names for the zone |
transfer |
project | Opts the zone into AXFR and IXFR zone transfers (see Zone Transfers) |
service |
instance | An extra service name every replica carrying it answers to |
aliases |
instance | Comma-separated extra names, each created as a CNAME |
incus project set shop user.label.dns.scope=global
incus project set shop user.label.dns.zone=shop.example.org
incus project set shop user.label.dns.transfer=true
incus config set web user.label.dns.service=api --project shop
incus config set web user.label.dns.aliases=alias1,me.example.com. --project shop
incus project set shop user.label.dns.ns=ns1.example.org.,ns2.example.org.
A trailing dot denotes an absolute domain name; without a trailing dot, names are relative to the zone.
A name ending in a dot is absolute; anything else is relative to the instance's
zone. For example, web in project shop with
user.label.dns.aliases=alias1,me.example.com. answers to alias1.shop.incus.
and me.example.com., both as a CNAME onto web.shop.incus..
ic-dns discovers eligible projects in this order:
--project names them explicitly on the CLI / environment.--project-marker (default:
user.label.dns.scope=global) are opted in.ic-dns supports authoritative zone transfers via standard DNS protocols
(AXFR for full transfer, IXFR for incremental transfer). This allows
external secondary nameservers (such as BIND9, PowerDNS, Knot DNS, or public
secondary DNS providers) to replicate and serve the project zone.
sequenceDiagram
participant Sec as Secondary DNS (BIND/PowerDNS)
participant DNS as ic-dns (:53 TCP)
participant Incus as Incus Fleet
Note over Sec,DNS: Gate 1: Peer IP matches --allow-transfer CIDRs<br/>Gate 2: Project has user.label.dns.transfer=true
Sec->>DNS: TCP AXFR / IXFR query for shop.example.org
DNS->>DNS: Validate peer IP against allowlist & verify zone transfer enabled
DNS-->>Sec: Stream zone records bracketed by SOA apex
Note over Sec: Secondary loads complete zone with current serial
Zone transfers are strictly opt-in and protected by two independent gates:
--allow-transfer): The daemon must be started with
--allow-transfer (or INCUS_COMPOSE_DNS_ALLOW_TRANSFER) specifying the
CIDR prefixes of allowed secondary nameservers (e.g.
192.168.1.0/24,10.0.0.53/32). An empty list allows nobody and refuses all
transfer requests.user.label.dns.transfer): The Incus project owning the zone
must be explicitly opted in:
incus project set shop user.label.dns.transfer=true
Both gates must evaluate to true. If either gate is closed, transfer requests
are refused (REFUSED).
When using x-incus-compose.dns, specifying allow_transfer automatically
satisfies both gates: it starts the daemon with --allow-transfer and labels
the Incus project with user.label.dns.transfer=true. When using a shared
global DNS sidecar, individual projects can opt into transfers by specifying
transfer: true in x-incus-compose.dns.
shop.example.org). Requests for individual subdomains or invented shadowing
zones (such as absolute external aliases) are refused.incus-compose dns status [options]
| Option | Description | Default | Environment Variable |
|---|---|---|---|
--format |
Output format: text or json |
text |
INCUS_COMPOSE_DNS_STATUS_FORMAT |
--port |
HTTP port to query on the sidecar | 9153 |
INCUS_COMPOSE_DNS_HTTP_PORT |
--metrics |
Include Prometheus metrics in the status report | false |
INCUS_COMPOSE_DNS_STATUS_METRICS |
Default text format:
Status: ready
IPv4: 10.90.190.53
IPv6: fd42::53
JSON format (--format json):
{
"status": "ready",
"ipv4": "10.90.190.53",
"ipv6": "fd42::53"
}
incus-compose dns logs [--follow]
Streams or prints logs directly from the ic-dns sidecar instance.
incus-compose dns up [options]
Spins up or recreates the ic-dns sidecar instance, mounting necessary volumes
and establishing certificates.
| Option | Description | Default | Environment Variable |
|---|---|---|---|
--allow-transfer |
CIDR(s) that may ask for a zone transfer (empty allows nobody) | INCUS_COMPOSE_DNS_ALLOW_TRANSFER |
incus-compose dns down [--force]
Stops and deletes the ic-dns sidecar instance. When multiple projects share a
global ic-dns, --force confirms removal without an interactive prompt.
incus-compose dns.