umbrel icon indicating copy to clipboard operation
umbrel copied to clipboard

`.local` domain for each app using mDNS

Open giladgd opened this issue 9 months ago • 3 comments

It would be nice to have a different .local domain for each installed app instead of using a different port for each one.

To do this, you can use the following approach:

  • Use the multicast-dns package to resolve a given domain to the device running umbrelOS.
  • Use Traefik/Nginx or a custom proxy implementation in node.js to route incoming traffic based on the domain to the right service.

You can also make sure that each app gets a "subdomain" of umbrel.local (e.g. homebridge.umbrel.local). There's no such concept of a subdomain for mDNS, but you can make it work with the same convention.

I'm already using this approach on my Raspberri Pi and it works great. The nice thing about it is that I have multiple domains on my local network, without configuring any device with a custom DNS server or anything.

To resolve any .local domain you want, you can use this code snippet:

import os from "os";
import mdns from "multicast-dns";

const additionalHostnames = new Set([
    "homebridge.local",
    "umbrel.local",
    "homebridge.umbrel.local"
]);
const hostnameTtl = 300;

const mdnsResponder = mdns();

mdnsResponder.on("query", async (query) => {
    const answers = [];
    for (const question of query.questions) {
        if (additionalHostnames.has(question.name) && question.type == "A") {
            const ip = getNetworkIp();
            if (ip == null)
                continue;

            console.debug(`Answering to "${question.name}" mDNS query with IP "${ip}"`);
            answers.push({
                name: question.name,
                type: "A",
                ttl: hostnameTtl,
                data: ip
            });
        }
    }

    if (answers.length > 0)
        mdnsResponder.respond(answers);
});

function getNetworkIp() {
    const ifaces = os.networkInterfaces();
    for (const iface of Object.values(ifaces)) {
        for (const alias of iface) {
            if (alias.family === "IPv4" && alias.address !== "" && !alias.internal)
                return alias.address;
        }
    }

    return null;
}

I run the mDNS responder on the device itself and not inside of a docker container, so you might need some additional configuration to make it work through docker.

I think this would significantly improve the experience of using umbrelOS, and I wanted to show how easy it is to implement the resolver.

One implementation detail that you should take into account it that trying to resolve a .local domain from inside a docker container doesn't work without intervention. You can either append the relevant domains to the /etc/hosts file of the host machine (to make it available to all containers, with the machine's external static IP) or by using the extra_hosts key in a docker compose service. For example:

services:
  homebridge:
    image: ...
    ...
    extra_hosts:
      - "homebridge.local:host-gateway"
      - "homebridge.umbrel.local:host-gateway"

giladgd avatar Feb 09 '25 16:02 giladgd

Expanding on this and making it more flexible, I think there should be a way to configure some data in each app compose file. Adding a custom url is one of them, people will use different methods for that, but after doing it, there should be a way to use that url on umbrel dashboard as well.

On this broader thing of customizing the compose we could even have env variables as well because multiple apps use those for advanced configuration. Today I can edit on the command line but all edits are lost upon updating umbrel.

kauelima avatar Feb 19 '25 10:02 kauelima

Well, it can be a feature, but instead of this, why not use a reverse proxy to try and access it from the internet? Reverse-proxy doesn't need any modifications on the docker-compose.yml file and can also work after updating apps without the need of any modification.

IMPranshu avatar Mar 05 '25 12:03 IMPranshu

this seems like a great idea, adding some more info that is hopefully relevant,

I did a bunch of investigating on this and hopefully it helps for other hostnames too, there may already be some mDNS logic being built and i see some mDNS stuff in logs but at least maybe informs some ideas for a future PR for the team

i just summarized some of the changes to maybe explore as well below...

This might also help resolve a lot of issues in browsers resolving specifically like chrome without having to clear their cache or make any client-side changes...

For instance, I think a lot of users have issues in Chrome loading umbrel.local and may not be able to immediately find the IP

This is the error i consistently get in Chrome (but the IP works fine), and Safari works fine for umbrel.local Check if there is a typo in umbrel.local DNS_PROBE_FINISHED_NXDOMAIN

and running the IP of my Umbrel to map the IP to Umbrel resolves it and immediatley fixes it echo "192.168.1.196 umbrel.local" | sudo tee -a /etc/hosts

(given you used arp -a command or ssh'd into umbrel to find its correct IP for the command above, see Umbrel FAQ)

Background on resolving issue in Chrome:

Safari has native support for mDNS .local hostname resolution built directly into macOS’s system resolver. This means when you try to visit umbrel.local or homebridge.local in Safari, the browser relies on the OS’s multicast DNS responder, which automatically resolves these .local names to the correct IP on your local network without extra setup.

Chrome, however, handles .local resolution differently. Although it can use the system DNS resolver on macOS, it also enforces its own DNS-over-HTTPS (DoH) and Secure DNS policies by default. This can interfere with .local name resolution because .local domains are meant to be resolved via mDNS (local multicast), not standard DNS queries sent to external servers. As a result, Chrome may fail to resolve .local hostnames or behave inconsistently unless Secure DNS is disabled or specific workarounds (like editing /etc/hosts) are used

By default, Microsoft Edge and Brave Browser also enforce Secure DNS (DNS-over-HTTPS) and their own DNS resolution policies, this means they often do not properly resolve .local mDNS names out-of-the-box, just like Chrome.

Summary of some possible changes

Network change could introduce a local mDNS responder on the Umbrel device that supports multiple custom .local hostnames for individual apps, such as homebridge.umbrel.local, bitcoin.umbrel.local, and lndhub.umbrel.local.

By doing so, each app can be accessed via a distinct, user-friendly domain instead of relying on ports or manual /etc/hosts edits on the Mac. This approach helps resolve issues specifically seen in Chrome where .local resolution is inconsistent, allowing Chrome and other browsers to resolve these domains directly via mDNS without extra configuration on the client side.

i.e.

const additionalHostnames = new Set([
    "homebridge.local",
    "umbrel.local",
    "homebridge.umbrel.local",
    // "portainer.umbrel.local",   // Example: Access Portainer UI via its own clean local domain
    // "nextcloud.umbrel.local",   // Example: Access Nextcloud locally without relying on port numbers
    //
    // Defining custom .local hostnames for apps allows each service to be accessed
    // using a dedicated, user-friendly domain like nextcloud.umbrel.local instead of umbrel.local:port.
    // This improves compatibility in browsers like Chrome, Edge, and Brave,
    // which may not resolve .local subdomains correctly without a dedicated mDNS responder.
]);

Implementing this would improve the user experience by simplifying access to Umbrel apps and avoiding the need for manual network or device configuration.

Some of this logic may already exist though for resolving ports and may be done practically some other way, hopefully it just informs some ideas on how to make some of the experience better for anyone having issues loading in different browsers or any homelabers who wanted a little more control to change the domain

Parts of the Umbrel repo this may affect:

umbrelOS (mDNS responder service) Add a lightweight mDNS responder service (e.g., using the multicast-dns Node.js package) to the OS that listens for .local queries and responds with the Umbrel device’s IP for each app’s custom hostname.

Umbrel Dashboard / App Compose Configuration

Extend the dashboard and app deployment manifests (Docker Compose files) to allow defining custom .local hostnames per app, enabling the proxy to route traffic accordingly.

This may add too much clutter but could be an option in the settings, or even somewhere near the troubleshoot of settings, as users may likely be navigating there when opening in alternate browsers to make changes and can see something to easily set it or click on something that adjusts their client side host settings-- which could be a cool feature to add.

Reverse Proxy (Traefik/Nginx) Config

Modify proxy configuration to route incoming HTTP requests based on the requested hostname (e.g., homebridge.umbrel.local) to the correct Docker container port for that app.

Umbrel Docker Host Network / Docker Compose files

Update Docker networking or add extra_hosts entries as needed to ensure containers can resolve the hostnames internally and that traffic routing works seamlessly.

Together, these changes enable seamless local DNS resolution for multiple Umbrel apps without manual host edits on client machines and improve compatibility with Chrome and other browsers, greatly increasing the user experience so umbrel.local and both the dashboard and applications always resolve in the browser...

Some of this could be accomplished for sure in other more practical ways, but could be innovative to reduce the amount of troubleshooting on the client machine...

jimbrend avatar Jun 16 '25 08:06 jimbrend