How to Self-Host Plausible Analytics With Docker Compose
Plausible Community Edition (CE) is the self-hosted version of Plausible, a web analytics service. Docker Compose uses a configuration file to run…
On a Linux host, Docker may fail to start AdGuard Home or Pi-hole because systemd-resolved already uses port 53 for DNS. A port is a numbered endpoint that services use to receive network traffic. If the DNS server should serve your LAN, disable systemd-resolved’s local DNS listener, keep the host’s DNS configuration working, and publish both TCP and UDP port 53 from the container.
systemd-resolved is the Linux service that handles DNS lookups for the host. Its DNS stub listener is a local endpoint for programs on that host. The systemd documentation says the stub listens on 127.0.0.53 and 127.0.0.54 using both TCP and UDP. Docker’s default port publishing can expose a container port across the host’s addresses, which can create a conflict with that listener.
Before changing settings, find out which service owns port 53 and how the host resolves names:
sudo ss -lntup 'sport = :53'
readlink -f /etc/resolv.conf
resolvectl status
The ss command shows listening sockets, including their addresses and protocols. Check its output for systemd-resolved and for any other DNS service or container using port 53. The readlink and resolvectl commands show where the host gets its DNS settings.
This check matters because disabling the stub does not automatically give the host another resolver. If /etc/resolv.conf still directs programs to 127.0.0.53, those programs lose DNS when the stub stops listening.
Use a systemd drop-in file to turn off the stub listener without editing the main configuration file:
sudo mkdir -p /etc/systemd/resolved.conf.d
printf '[Resolve]\nDNSStubListener=no\n' \
| sudo tee /etc/systemd/resolved.conf.d/no-stub.conf
The DNSStubListener=no setting disables the local listener; it does not disable the systemd-resolved service. Keeping the service running matters on hosts that depend on it for network or VPN name resolution.
Next, check how your distribution manages /etc/resolv.conf. Pi-hole’s Docker instructions for this conflict use /run/systemd/resolve/resolv.conf, which contains the configured DNS servers rather than the local stub address. On systems where that is the appropriate resolver file, the change is:
sudo rm -f /etc/resolv.conf
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved
Do not replace /etc/resolv.conf blindly. Distributions and network managers handle that file differently, and some manage it as a symlink. Confirm your existing setup and follow your distribution’s resolver instructions before changing it.
After the restart, check readlink -f /etc/resolv.conf and confirm that the file does not direct the host only to 127.0.0.53. Then test a host lookup:
getent hosts example.com
If the command returns an address, the host can resolve that name. If it fails, fix the host’s resolver configuration before relying on the DNS container.
With Docker bridge networking, Docker gives the container a separate network stack. Publishing a port lets clients reach a container service through a port on the host. DNS needs both TCP and UDP mappings:
ports:
- "53:53/tcp"
- "53:53/udp"
Both Pi-hole’s Docker quick start and AdGuard Home’s Docker instructions show TCP and UDP mappings for DNS. Add mappings for other ports only for features you plan to use, such as a web interface or DHCP. Pi-hole’s current quick start uses v6-style configuration variables, so check the documentation for the version you deploy. Its example also sets the DNS listening mode to ALL when using Docker’s default bridge network.
A mapping without a specific host address can expose the port on all host addresses. Docker’s port publishing documentation explains how to bind a published port to a particular host IP instead. Restrict access to the addresses and networks where clients should reach the DNS service, and use suitable firewall rules.
Once the container is running, test DNS against the host’s LAN address from the host and from a LAN client. If dig is installed, query both protocols:
dig @"$DNS_SERVER_IP" example.com
dig +tcp @"$DNS_SERVER_IP" example.com
Set DNS_SERVER_IP to the host’s LAN address before running the commands. A successful UDP query alone does not confirm that the TCP mapping works. Test from a separate LAN device before changing the router’s DNS setting for all clients.
If you want Docker to publish DNS only on the host’s LAN address, set that address in the Compose mapping:
ports:
- "${LAN_IP}:53:53/tcp"
- "${LAN_IP}:53:53/udp"
Set LAN_IP to an address assigned to the host and keep it stable. Docker supports publishing to a specific host IP, but whether this avoids a conflict depends on the host’s listener bindings and Docker configuration. Treat it as an option to test on your host rather than a guaranteed fix.
Another option is to give the container its own LAN address with a macvlan network. Macvlan connects a container to the LAN as a separate network device, which avoids publishing port 53 on the host. Pi-hole describes macvlan as an advanced networking option in its Docker networking guidance. Your network layout determines the configuration, so check that LAN clients and the host can reach the container at its new address.
Host networking does not resolve this conflict by itself. With Docker’s host network mode, the container uses the host’s network interfaces and ports directly. The container and systemd-resolved then compete for the host’s port 53. Docker explains this behavior in its host network documentation. Host networking may suit other needs, such as DHCP broadcasting, but it is not a port-conflict fix.
If the host uses Pi-hole as its DNS server, Pi-hole still needs a working upstream DNS server to answer queries. Otherwise, the host can lose name resolution when Pi-hole is down, including the lookups you need to manage or restart services. Pi-hole’s Docker guidance describes this dependency as a potential “catch-22.” Configure a recovery path and avoid making the DNS container depend on itself for upstream DNS.
DHCP is a separate concern from serving DNS. You do not need to enable DHCP just to resolve the port conflict. If you plan to have Pi-hole distribute network addresses, account for the extra network requirements; Pi-hole notes that bridge networking can require a DHCP relay.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com