Running Unifi Network Application in an Incus container

Setting up the Unifi Network Application and MongoDB using Quadlets in an Alma Linux Incus container.

Running Unifi Network Application in an Incus container

In my previous post I set up an Incus node.

Setting up Incus with ZFS on NixOS
In this guide i setup a NixOS system with ZFS root and bridged networking for use as an Incus container/VM host.

Now i want to start running system containers in Incus. The first container i'm going to run is for the Unifi Network Application and the associated Mongo database.

The container will have its own IP on the local network with the Unifi and MongoDB services run via Podman.

I'm using an Alma Linux 9 container as the point releases ship with a modern version of Podman.

The Incus container

First log into the Incus node and create the container.

incus launch images:almalinux/9 unifi --profile bridged

Now let's get the MAC address of the Unifi container

incus info unifi

Find the MAC address and then reserve an IP address for that MAC on your router.

Configuring the Unifi container

Get a shell on the container to begin configuration

incus shell unifi

Install an editor and Podman

dnf install nano podman -y

The Unifi Network Application expects a pre-existing Mongo database so let's set that up now.

MongoDB

Create the MongoDB initialisation script

nano -w init-mongo.sh

and copy/paste the following:

#!/bin/bash

if which mongosh > /dev/null 2>&1; then
  mongo_init_bin='mongosh'
else
  mongo_init_bin='mongo'
fi
"${mongo_init_bin}" <<EOF
use ${MONGO_AUTHSOURCE}
db.auth("${MONGO_INITDB_ROOT_USERNAME}", "${MONGO_INITDB_ROOT_PASSWORD}")
db.createUser({
  user: "${MONGO_USER}",
  pwd: "${MONGO_PASS}",
  roles: [
    { db: "${MONGO_DBNAME}", role: "dbOwner" },
    { db: "${MONGO_DBNAME}_stat", role: "dbOwner" }
  ]
})
EOF

This script comes from the LinuxServer page for the Unifi Network Application Docker image.

Now create a volume for the DB

podman volume create unifi-db

and launch MongoDB for initialisation

podman run --rm \
-e MONGO_INITDB_ROOT_USERNAME=root \
-e MONGO_INITDB_ROOT_PASSWORD=itsasecret \
-e MONGO_USER=unifi \
-e MONGO_PASS=itsasecret \
-e MONGO_DBNAME=unifi \
-e MONGO_AUTHSOURCE=admin \
-v unifi-db:/data/db:Z \
-v /root/init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh:ro \
docker.io/mongo:7.0

Change the passwords as you see fit, but remember them because you'll need them later.

Wait a minute while the DB is initialised then do a ctrl-c to shut things down and rm init-mongo.sh to remove the initialisation script.

Quadlet files

Now that the database is ready it's time to set up the Unifi and Unifi-DB OCI containers. We're going to use Quadlet files for this.

First change directories

cd /etc/containers/systemd

We have several files to create. Create the files as named then copy/paste the contents:

unifi-app.volume

[Volume]
VolumeName=unifi-app

unifi-db.volume

[Volume]
VolumeName=unifi-db

unifi.pod

[Pod]
PodName=unifi
PublishPort=8443:8443
PublishPort=8080:8080
PublishPort=3478:3478/udp
PublishPort=10001:10001/udp
PublishPort=1900:1900/udp
PublishPort=8843:8843
PublishPort=8880:8880
PublishPort=6789:6789
PublishPort=5514:5514/udp

[Install]
WantedBy=multi-user.target default.target

unifi-app.container

[Container]
ContainerName=unifi-app
Image=lscr.io/linuxserver/unifi-network-application:latest
AutoUpdate=registry
Environment=MONGO_USER=unifi
Environment=MONGO_PASS=itsasecret
Environment=MONGO_DBNAME=unifi
Environment=MONGO_AUTHSOURCE=admin
Environment=MONGO_HOST=unifi-db
Environment=MONGO_PORT=27017
Environment=MEM_LIMIT=1024
Environment=MEM_STARTUP=1024
Environment=TZ=Europe/London
Volume=unifi-app.volume:/config:Z
Pod=unifi.pod

[Unit]
After=unifi-db.service

unifi-db.container

[Container]
ContainerName=unifi-db
Image=docker.io/mongo:7.0
AutoUpdate=registry
Environment=MONGO_INITDB_ROOT_USERNAME=root
Environment=MONGO_INITDB_ROOT_PASSWORD=itsasecret
Environment=TZ=Europe/London
Volume=unifi-db.volume:/data/db:Z
Pod=unifi.pod

Finally set up automatic updates for your containers

systemctl enable podman-auto-update.{service,timer}

Now let's exit the container shell

exit

and restart it so that it can get its new IP from the router and start the Unifi services

incus restart unifi

Go to https://CONTAINER-IP-ADDRESS:8443 to setup Unifi

Extras (Tailscale, TLS, System Updates)

The above gets you a functional Unifi Network Application setup but i want a bit more. I want to access my Unifi controller from anywhere, i don't want TLS certificate warnings and i want the system container to stay up to date. Here's how to do that.

Get a shell on your Unifi container

incus shell unifi

We're going to use Caddy as a reverse proxy for the Unifi web server. Caddy has built-in support for getting LetsEncrypt certificates, including using Tailscale integration.

Add the Caddy and Tailscale repos then install them along with automatic updates

dnf install 'dnf-command(copr)' -y
dnf copr enable @caddy/caddy -y
dnf config-manager --add-repo https://pkgs.tailscale.com/stable/centos/9/tailscale.repo
dnf install tailscale caddy dnf-automatic -y

Edit /etc/default/tailscaled and add TS_PERMIT_CERT_UID=caddy to the bottom.

Enable and authenticate Tailscale

systemctl enable --now tailscaled
tailscale up

Create/edit /etc/caddy/Caddyfile replacing TAILNET-NAME to be your actual tailnet name:

unifi.TAILNET-NAME.ts.net {

	file_server

	reverse_proxy https://localhost:8443 {
		transport http {
			tls
			tls_insecure_skip_verify
		}
	}
}

Enable Caddy

systemctl enable --now caddy

After a minute or so Caddy should get a certificate and you can now access your Unifi controller at https://unifi.TAILNET-NAME.ts.net. If your router supports it you can set up a DNS override for unifi.TAILNET-NAME.ts.net so you can still access the controller at this address in the event your Internet or Tailscale is down.

For keeping the system container updated edit /etc/dnf/automatic.conf and change apply_updates to yes.

Then enable the automatic updates

systemctl enable --now dnf-automatic.timer

Container snapshot

Now that the Unifi container is running as we want let's create snapshot of the known good state so we can roll back if future updates break things.

On the Incus node

incus snapshot create unifi initial