[LXD] Custom DNS records in network zones

Project LXD
Status Implemented
Author(s) @stgraber
Approver(s) @sdeziel @tomp
Release LXD 4.23
Internal ID LX013


LXD currently supports generating DNS zones for instances (forward and reverse records), but it doesn’t support adding other DNS records.

This will add API and CLI to allow a user to directly add DNS records of any types into their LXD managed network zones.


Currently a user who wants to fully utilize a DNS zone and put both instance records and regular records (A/AAAA/CNAME/TXT/…) will need to use LXD’s network zone feature, transfer the zone somewhere else, alter it and publish the result.

This is far from ideal and makes it pretty hard to publish a simple TXT record as needed for many zone validation mechanisms as well as for some TLS certificate issuance methods.



This will be a reasonably simple extension to the existing network zones concept, it will add a REST API to manage records on a zone and matching CLI to add/update/remove records.

API changes

A new /1.0/network-zones/<ZONE>/records endpoint will be added, supporting:

  • GET /1.0/network-zones/<ZONE>/records
  • GET /1.0/network-zones/<ZONE>/records?recursion=1
  • POST /1.0/network-zones/<ZONE>/records
  • GET /1.0/network-zones/<ZONE>/records/<RECORD>
  • PUT /1.0/network-zones/<ZONE>/records/<RECORD>
  • PATCH /1.0/network-zones/<ZONE>/records/<RECORD>
  • DELETE /1.0/network-zones/<ZONE>/records/<RECORD>

The structs associated with a record will be something like:

type NetworkZoneRecord struct {
    Description string `json:"description" yaml:"description"`
    Name string `json:"name" yaml:"name"`
    Entries []NetworkZoneRecordEntry `json:"entries" yaml:"entries"`
    Config map[string]string `json:"config" yaml:"config"`

type NetworkZoneRecordEntry struct {
    Type string `json:"type" yaml:"type"`
    TTL uint64 `json:"ttl" yaml:"ttl"`
    Value string `json:"value" yaml:"value"`

This allows for both round-robin and dual-stack records.
Complex records like MX/SRV will be serialized as a string (e.g. <priority> <target>)
The Config map will initially just support the user.* keys but may be extended in the future to allow restricting the scope/visibility of a record or to tweak things like the TTL.

CLI changes

The lxc network zone set of commands will be expanded with:

  • lxc network zone record list <zone>
  • lxc network zone record create <zone> <record> <type> <value>
  • lxc network zone record delete <zone> <record>
  • lxc network zone record show <zone> <record>
  • lxc network zone record edit <zone> <record>
  • lxc network zone record get <zone> <record>
  • lxc network zone record set <zone> <record>
  • lxc network zone record unset <zone> <record>

Database changes

This change will require two new tables:

  • network_zones_records
    • id (int64)
    • network_zone_id (int64)
    • description (string)
    • entries (json)
    • name (string)
  • network_zones_records_config
    • standard key/value schema

Upgrade handling

This is a standalone new feature which will not require any data conversion or upgrade handling.


@tomp @sdeziel can you see if that makes sense to you?

The spec looks good to me.

Responding publicly to a hidden comment from @tomp (most likely by accident).

Looks good to me.
Although how will it deal with MX priorities, and SRV priority weight port values? Will this be via config eventually?

For priorities/weight, my thought was to encapsulate that in the value as is somewhat standard in other such DNS implementations. 10 smtp1.stgraber.org. for example.

Hmm, so there’s a bit of a design flaw in here which we need to address.

Say you want to do a dual-stack record with an A and AAAA on foo.example.net.
With the current plan, this can’t be done as the record must be unique at /1.0/network-zones/example.net/records/foo and a record has a type and multiple values. The multiple values allows for round-robin but doesn’t allow for dual-stack as those are different record types.

So I think that we need to drop Type from the record itself, rename Value into Entries and make it a []NetworkZoneRecordEntry struct which then looks like:

type NetworkZoneRecordEntry struct {
    Type string `json:"type" yaml:"type"`
    TTL uint64 `json:"ttl" yaml:"ttl"`
    Value string `json:"value" yaml:"value"`

This then let’s me have:

  user.foo: bar
description: Primary web server
 - type: A
   ttl: 1800
 - type: A
   ttl: 1800
 - type: AAAA
   ttl: 1800
   value: 2001:db8::1
 - type: AAAA
   ttl: 1800
   value: 2001:db8::2
name: rproxy

Thoughts @sdeziel @tomp ?

1 Like

Yeah, that sounds like a good solution to me. Glad you caught this one!

I’ve updated the spec itself to reflect this change.

Not sure if this post covers it but its the same for TXT records which can be stacked (if you generate a wilcard lets encrypt cert it asks you to deploy multiple TXT records)

Yep, that will work.

Going to have to add some more CLI commands for the entries:

  • lxc network zone record entry add
  • lxc network zone record entry remove

That kinda lines it up with the lxc network acl rule behavior.

Sounds good thanks