Running Pi-hole in LXD container

This guide was originally written for those who:
a) would like to try Pi-hole Network-wide Ad Blocking locally before buying a hardware
b) would like to use Pi-hole locally, only on one device

Later I decided that Pi-hole fits nice with LXD container, and added UFW configuration to allow requests from all clients.

What do we gain here from using LXD container?
This allows Pi-hole web-interface on port 80 to coexist with any existing web-app on host, increases security for web-interface open to Internet, and simplifies deletion when testing is over.

Installing LXD and Pi-hole

First, install LXD
v. 3.0 LTS release, Ubuntu 18.04
sudo apt install lxd or
v. 3.18 or newer (feature releases), Ubuntu 18.04 and newer, Debian 10, Arch Linux, or any other Linux that supports snaps
sudo snap install lxd

Do first time setup. Default answers are fine.
lxd init

From now on, use lxc command.
Create and start an Ubuntu 18.04 container named pihole
lxc launch ubuntu:18.04 pihole

Check that it started succesfully and has an IP
lxc list

Log into container, set the correct timezone
lxc exec pihole -- /bin/bash
dpkg-reconfigure tzdata
and install Pi-hole using the official guide.

Save the password shown by installer.
Log out: Ctrl + D

Tell the system to use the Pi-hole container as new DNS*
Go to internet connection settings and set container IP as DNS.
You can look up the IP:
lxc list

Restart your browser and open any website, then open Pi-hole admin page (it is on the same IP as DNS), log in and see your browser`s request in Query Log.

Optional: Allow connections from your network to container

Use UFW to allow connections to Pi-hole from other devices.
Source: https://gist.github.com/kimus/9315140

Edit /etc/default/ufw :
DEFAULT_FORWARD_POLICY="ACCEPT"

Edit /etc/ufw/sysctl.conf :
net.ipv4.ip_forward=1

Add the following to /etc/ufw/before.rules just before the filter rules, be sure to replace wlp1s0 with your network interface and 10.81.65.206 with your container IP, 192.168.1.45 with your host IP.

# NAT table rules
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]

# Port Forwardings
-A PREROUTING -i wlp1s0 -p tcp --dport 80 -j DNAT --to-destination 10.81.65.206
-A PREROUTING -i wlp1s0 -p tcp --dport 53 -j DNAT --to-destination 10.81.65.206
-A PREROUTING -i wlp1s0 -p udp --dport 53 -j DNAT --to-destination 10.81.65.206

# Allow access by local IP via wlp1s0 - change to match you out-interface
-A POSTROUTING -s 192.168.1.0/24 -o wlp1s0 -j SNAT --to-source 192.168.1.45
-A OUTPUT -d 192.168.1.45 -p tcp --dport 80 -j DNAT --to-destination 10.81.65.206

# don't delete the 'COMMIT' line or these nat table rules won't
# be processed
COMMIT

Enable changes by rebooting the host machine

You can now use Pi-hole in your network.

Removing container

Roll back whatever changes you did to network connection and UFW.
Stop and delete container:
lxc stop pihole
lxc delete pihole
Optionally delete LXD
sudo apt purge lxd
or sudo snap remove lxd

Tips

* You can also set DNS for all internet connections.
Here is the how-to for NetworkManager.
Edit /etc/NetworkManager/NetworkManager.conf
Add:
<main>
dns=none

This will instruct NetworkManager to not make any changes to /etc/resolv.conf
Restart NetworkManager
sudo systemctl restart NetworkManager

Now edit /etc/resolv.conf
Delete everything, and put only:
nameserver your-container-ip