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.
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
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 :
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!