grishy/go-avahi-cname

Lightweight mDNS publisher of subdomains for your machine 🪶

★ 124Forks 5GoGitHub ↗Compare
aliasesavahicnamegolangmdnsmulticastself-hostedsubdomain

README


Status: Maintained

GitHub tag (with filter) Go Report Card Build Status

TL;DR: How to Start in Subdomain Mode

Redirect all subdomains to the machine (*.hostname.local → hostname.local).

  1. Binary (download from Releases):

    ./go-avahi-cname subdomain
  2. Docker:

    docker run -d --network host -v "/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket" ghcr.io/grishy/go-avahi-cname:2.7.0

    Note: If you encounter issues with AppArmor, add --security-opt apparmor=unconfined to the Docker command or include the following in your Docker Compose file:

    security_opt:
      - apparmor=unconfined

What is go-avahi-cname?

It is a simple and lightweight project that allows you to publish CNAME records pointing to the local host over multicast DNS using the Avahi daemon, which is widely available in most Linux distributions. This means that you can access your local host using different names from any device on the same network, as long as they support mDNS.

Goals

  • ✅ No dependencies - Requires only the Avahi daemon.
  • ✅ Small footprint
  • ✅ Support x86_64 and ARM
  • ✅ Install as binaries or containers

Used By

This project is used in production by PlanktoScope (scientific imaging platform), openUC2 (open-source microscopy), and many other small personal projects.

Modes of Operation

  • Subdomain reply - I think you want this. Listen to the traffic and if someone asks *.hostname.local (example: name1.hostname.local), we "redirect" to hostname.local.
  • Interval publishing - Keeps CNAME records registered for various name1.hostname.local, git.any.local...

How does it work?

The tool communicates with the Avahi daemon via DBus to publish CNAME records.

Subdomain CNAME reply

Architecture

> ./go-avahi-cname subdomain -h
NAME:
   go-avahi-cname subdomain - Listen for all queries and publish CNAMEs for subdomains

USAGE:
   go-avahi-cname subdomain [options]

OPTIONS:
   --ttl uint     TTL of CNAME record in seconds (default: 600) [$TTL]
   --fqdn string  FQDN which will be used for CNAME. If empty, will be used current FQDN from Avahi (default: <hostname>.local.) [$FQDN]
   --help, -h     show help

GLOBAL OPTIONS:
   --debug, -d  enable debug logging [$DEBUG]

In this variant, we listen to the traffic with avahi-daemon for all questions with names and if they match ours, we send a command to avahi to answer it (send CNAME). The standard can be run without parameters, then we will resolve all requests that contain our hostname. For example, git.lab.local will be redirected to lab.local

Subdomain mode retains up to 256 recently queried names. At capacity, a new name replaces the least recently queried registration. An evicted name can be registered again on its next query, which may introduce a short resolution delay. --ttl controls DNS client caching, not how long registrations are retained.

Interval publishing of CNAME records

Architecture

As you can see, go-avahi-cname communicates with the Avahi daemon via DBus, and publishes the CNAME records that you specify as arguments.

> ./go-avahi-cname cname -h
NAME:
   go-avahi-cname cname - Announce CNAME records for host via avahi-daemon

USAGE:
   go-avahi-cname cname [options]

OPTIONS:
   --ttl uint       TTL of CNAME record in seconds. How long they will be valid. (default: 600) [$TTL]
   --interval uint  Interval for refreshing CNAME registrations in seconds. (default: 300) [$INTERVAL]
   --fqdn string    where to redirect. If empty, the Avahi FQDN (current machine) will be used (default: <hostname>.local.) [$FQDN]
   --help, -h       show help

GLOBAL OPTIONS:
   --debug, -d  enable debug logging [$DEBUG]

Explicit CNAMEs stay registered until the process exits; they are not subject to the subdomain mode's 256-name retention limit. Avahi's configured resource limits still apply. --interval refreshes registrations without withdrawing them. You can use either just the name (name1), which will create a record as a subdomain for the current machine, or you can write the full FQDN (name1.hostname.local. domain with a dot on the end) format.

Options can appear before or after names, for example go-avahi-cname cname git --ttl 60. Use -- to stop option parsing explicitly.

For example, if your machine’s hostname is lab, you can run:

> ./go-avahi-cname cname git photo.local. example.lab.local.
18:08:55 INF creating publisher
18:08:55 INF getting FQDN from Avahi
18:08:55 INF running CNAME publisher fqdn=lab.local.
18:08:55 INF formatting CNAMEs
18:08:55 INF formatted CNAME cname=git.lab.local. note="added FQDN"
18:08:55 INF formatted CNAME cname=photo.local.
18:08:55 INF formatted CNAME cname=example.lab.local.
18:08:55 INF publishing CNAMEs interval=300 ttl=600

This will create three CNAME records pointing to your local host:

  • git.lab.local.
  • photo.local.
  • example.lab.local.

You can then access your local host using any of these names from other devices on the same network.

Installation options

There are two ways to install and run go-avahi-cname:

Binary

Binary files can be taken as artifacts for the Release. In this case, it would be better to create a systemd service.

Container

The images for each version are in the Packages section.
You need to provide the /var/run/dbus/system_bus_socket file to the container to be able to communicate with the host's Avahi daemon.

Docker Compose example:

version: "3.3"
services:
  go-avahi-cname:
    network_mode: host
    security_opt:
      - apparmor=unconfined # required if AppArmor restricts access
    volumes:
      - "/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket"
    image: "ghcr.io/grishy/go-avahi-cname:2.7.0"

If AppArmor denies access, include the security_opt section as shown above.

Ansible task to run the container:

- name: go-avahi-cname | Start container
  community.docker.docker_container:
    name: "go-avahi-cname"
    image: "ghcr.io/grishy/go-avahi-cname:2.7.0"
    restart_policy: unless-stopped
    network_mode: host
    security_opts:
      - "apparmor=unconfined" # required if AppArmor restricts access
    volumes:
      - "/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket" # access to avahi-daemon

Listener failures are retried internally. Avahi/D-Bus publication failures exit with an error so a Docker restart policy or systemd Restart=on-failure can restart the process.

Debugging

If you have any problems, you can run the tool with the --debug flag to get more information about what is happening.
NOTE: --debug flag is available for both modes and should be used before the subcommand.

# Start app with debug logs in subdomain mode
./go-avahi-cname --debug subdomain

Also, you can use the avahi-browse tool to see the published records:

# List all services from the local network
avahi-browse -a

Or monitor DBus messages between the tool and the Avahi daemon, e.g. of issue #22

# Monitor DBus messages for Avahi
sudo busctl monitor org.freedesktop.Avahi

Release

Update version references, commit and push the changes, and wait for CI to pass. Then tag the checked commit; see Release Process.

git tag -a v2.7.0 -m "Release v2.7.0"
git push origin v2.7.0

Source of inspiration

Logo

Generated by mix of Go Gopher and Avahi logo.

License

© 2026 Sergei G.
This project is MIT licensed.

Built with ❤️ for data ownership, local-first, and open-source

Contributors

grishy

Issues