Local DNS for Docker Containers using Pi-hole + Portainer + Nginx Proxy Manager

How to use Pi-Hole to easy-to-remember DNS names for your local services.

Local DNS for Docker Containers using Pi-hole + Portainer + Nginx Proxy Manager

Like most people running Docker, I gathered quite a few containers running on my very professional Ubuntu server (it's an 8 year old Lenovo laptop sitting in my garage).

Because of this, I started to have a problem.

New containers I deploy want to use ports that I already have allocated on my host machine. This begged the question, how can I run multiple containers on one host that require the use of the same ports?

Well, enter Nginx Proxy Manager (NPM).

It’s a reverse proxy that has a clean UI, is easy to use, and runs in a container. The reverse proxy will help us by being the recipient service of our requests, and will forward them to the right container depending on the subdomain name entered. For example, instead of typing in portainerIP:port to get to your Unifi Controller you could type unifi.domain.com which would forward your request to the Unifi Controller container. Moreover, this means you could run another container on the same Docker host that requires the same port, but because you'll be proxying requests to through NPM it’ll work just the same!

Ways to use NPM

Now, there are other great tutorials out there to use Nginx Proxy Manager to safely get your services exposed to the outside world using port forwarding at your router however, I’m not interested in getting access to these services outside of my home network that right now. So, for my example, I’ll be using pihole for my local DNS.

Pi-hole

This guide assumes you have pihole set up as your DNS server and Docker + Portainer installed already.

A Records

First step is to come up with a list of A records for pihole to know where to send traffic when a subdomain is queried. Since this is exclusively local traffic and we’re not worried about SSL/TLS (if you are, watch this video) you can pick any domain name you want.

For this guide I’ll be using the Unifi Controller container as our example. Here’s an example of the kind of A record we need:

portainer.domain.com → 10.x.x.x

^IP address of Docker host that will run NPM

CNAME Records

Next, we’re going to make list of our services (containers, in this case) that we’d like to point to the A record we just made. Here’s an example:

unifi.domain.com → portainer.domain.com
💡
The reason we do a CNAME record here instead of an A record is because these are services running on Portainer. The idea here is if we ever need to update the IP address of Portainer we only need to update one A record since the CNAME records of the containers are pointing to the domain portainer.domain.com and not the IP itself.​

Cool! Once all your services are all in pihole, we’re good to head over to Docker, install Nginx Proxy Manager, and get logged in. I’m not going to go into detail about the Nginx install process, other people have done a better job of that :

How to setup the Nginx Proxy Manager Docker example
Learn how to setup and install the Nginx Proxy Manager from Docker Hub. This simple tool greatly simplifies the configuration of Nginx reverse proxy servers, asset caching, host redirecting and SSL…

Docker + Portainer

Once you have Nginx Proxy Manager installed you’ll need to do a few important steps.

Let’s think about this logically - we made some changes on pihole, but what exactly is the impact of the changes we made?

  • Right now you can still access your containers via the host machine IP + port for the container.
  • Using a subdomain + domain like unifi.domain.com doesn’t get us to the Unifi Controller container, but if our DNS is working right it should be pointing us to the host machine for the service.

This means not only is Nginx not proxying the requests to those services, but the DNS entries we added aren’t really working. You’re probably wondering “does this guy even know what he’s doing?”

And don’t worry, the answer is kind of.

Container config

How do we make it so that we can only get to those services via Nginx?

First, we need to take note of and remove the port designations on the containers we’re working with (it’s a good idea to save these in case you need to roll back) so that they aren’t accessible directly.

Next, let’s make sure the containers we’re working with are on the same Portainer network as Nginx.

That should be it! Let’s head over to NPM.

Nginx Proxy Manager

In Proxy Hosts, add a Proxy Host. This is where we tell Nginx which container to send traffic to depending on the DNS name entered. In a correctly configured state it should send unifi.domain.com traffic to the Unifi Controller container (duh) and uptimekuma.domain.com traffic to the Uptime Kuma container (duh again).

We want to create a record for unifi.domain.com that forwards the traffic to the port that Unifi Controller requires to get to the webpage, in the case of the Unifi Controller it’s port 3000, like the screenshot below. We also can specify just the container name in the “hostname” section.

That should do it. Now try unifi.domain.com!

Pretty slick. Let me know if you have any questions, issues, or ways to make this better!