The purpose of this post is to provide a look at the configuration that I’m running in my own network for the Unifi Controller required for Ubiquiti UniFi equipment (wireless access points, switches, gateways). The intent here is to walk you through the process of converting to a containerized controllwe and discuss my docker-compose.yml
configuration choices. Following the steps in this article should get you a functional configuration for your own environment.
Introduction
Background
Upon initially purchasing Ubiquiti APs back in 2017, I made the decision to run my UniFi controller on a Raspberry Pi. This was a cool idea but, as updates would roll-in, I constantly found myself with a broken controller. There were Java version mismatches that would break the upgrade most commonly and then random other problems with why the controller just wouldn’t start. I could usually spend a day and recover everything, but it became a point of frustration.
After I decided to retire the Pi, I moved to having the controller virtualized on top of KVM running on Ubuntu. However, my Linux ecosystem in my lab is intentionally CentOS 7. Unfortunately, Ubiquiti seems to really only support Ubuntu/Debian for this controller and I wasn’t interested in having that in my ecosystem at that point (more to manage, different tools, etc).
As I was already experimenting with Docker in my lab for learning purposes, I set out to find a good way to run UniFi as a Docker container. I found that path from linuxserver.io in the form of the docker-unifi-controller project. I based my docker-compose.yml configuration on the one they provide and then added some modifications which you’ll see in this post.
Advantages to UniFi Containerization
You may wonder why it would be beneficial to containerize a UniFi controller using Docker (especially if you’re unfamiliar with container benefits in general). Here are some of the reasons I have an appreciation for this configuration:
- Containerizing my controller allows me to easily upgrade. The command we use in this post to handle starting the container will also update the container image as updates are available. It’s extremely painless and I have yet to have any issues with it.
- I very often tear-down my lab and rebuild it to test one thing or another (Ansible Playbooks for actually rebuilding it, usually). This approach makes getting my controller operational extremely fast without having to store a large VM image.
- I simply store my backup of the data volume and the configuration file and place it back on the rebuilt host (the rest is downloaded again for me by
docker-compose
).
- I simply store my backup of the data volume and the configuration file and place it back on the rebuilt host (the rest is downloaded again for me by
- Specifically in my configuration, the UniFi controller container is not port-forwarded like I’ve seen in other configurations. This should allow the container to discover other systems like it would in a traditional installation. This worked great for finding my APs recently.
Migrations from Old Controllers
If you already have a UniFi controller running, you’re going to want to migrate your data from that controller. This is a fairly simple matter using these steps at a high-level:
- Upgrade your current controller to at least something relatively current (to avoid configuration import issues).
- I personally never had issues even going from a 5.x to 6.x but Ubiquiti warns about this constantly so I imagine it’s a very real possibility.
- Download the latest backup from your existing controller in the UI (Settings > Backup > Download Backup).
- Shut down your old controller.
- Once the containerized controller is available, restore the configuration from the UI (Settings > Backup > Restore Backup).
Find detailed instructions from Ubiquiti on backup and restore here.
Prerequisites
- You should have a cursory understanding of Docker so that you can support your configuration.
- Your Docker host system will need to have an interface on the same VLAN you intend to have your UniFi controller operate.
- A static IP address for your host is not required but we will use one for the container image itself.
- You’ll need to go ahead and prepare your environment by installing Docker Engine.
- Once you have Docker installed (and running!), you’ll need to also go ahead and install
docker-compose
(available from the same repository as Docker).- Also ensure that the
dockerd
or similar service on your platform is set to start at boot.
- Also ensure that the
Controller Setup
Environment Configuration
Once you have an environment ready to host your configuration, you should make a directory where you plan to store your docker-compose
configuration. Follow these steps as the root
user:
- Change directories into your
docker-compose
configuration directory.- This is
/data/docker_profiles
for me.
- This is
- Create a new directory for your UniFi controller (such as
unifi
). - Create a new user to own your controller data:
useradd -u 1502 -g 1502 -c "UniFi Controller" -d /data/docker_profiles/unifi unifi
- The command above assumes you’ll use the same storage point I use:
/data/docker_profiles
. - This command also assumes that you’ll use UID and GID 1502 (my chosen values) for your UniFi user.
- The command above assumes you’ll use the same storage point I use:
Docker-compose Configuration
I’ve provided the docker-compose.yml
that I use below. Place this configuration (named docker-compose.yml
) into the new data directory you made for this container. The idea here is for you to copy this file as a basis (adjusting the fields that have placeholders below):
---
version: "2.4"
services:
unifi-controller:
image: ghcr.io/linuxserver/unifi-controller
container_name: unifi-controller
environment:
- PUID=1502
- PGID=1502
- MEM_LIMIT=1024M #optional
volumes:
- ./data:/config
restart: unless-stopped
networks:
unifi:
ipv4_address: <literal static address for controller>
networks:
unifi:
driver: macvlan
driver_opts:
parent: <network adapter where controller should go>
ipam:
config:
- subnet: "<literal definition of your network>"
ip_range: "<same address value as ipv4_address above>/32"
gateway: "<gateway address>"
Now let’s walk through the important parts of this configuration:
- We’re using
version: "2.4"
in order to ensure thatdocker-compose
will actually support ournetworks
configuration below. Version 3.0+ seems to have dropped support for this IP address configuration. - In the
environment
section, we’re defining thePUID
(user ID) andPGID
(group ID) that our process will use. The data this container creates will be owned by this user. - For
volumes
, we’re creating a./data
folder on the host that will be mounted at/config
within the container. We’ll want to backup the./data
folder for this container as this is where our configuration will persist. - We want to ensure that this container starts up automatically unless we stopped it so we use
restart: unless-stopped
to ensure this. - We need to assign our container to a network that we define (
unifi
) and give it a static IP address (ipv4_address
).- This should just be the straight IP address (such as
192.168.0.8
).
- This should just be the straight IP address (such as
- We have to define
unifi
using the drivermacvlan
, which is what allows it to directly access a network on the host system.- Special thanks to Sarunas Zilinskas for the clues in this post on the proper syntax for
docker-compose.yml
!
- Special thanks to Sarunas Zilinskas for the clues in this post on the proper syntax for
- We must define the name of the host network adapter we want to use for this network as
parent
(such asenp1s0
oreth0
). - For
subnet
, we must specify the actual size of our network as a CIDR address (such as192.168.0.0/24
for a traditional home network). - For
ip_range
, we’re simply defining a single address for this network that will only be used by this container. This needs to be the same value as theipv4_address
field above but with the CIDR notation at the end (so192.168.0.8/32
).
Controller Initialization
Now that you’ve configured everything to make it ready for your controller, you need to run your controller for the first time and make sure that it starts without an issue. This is fairly straight-forward if everything is configured properly.
From your container directory (so unifi
as defined above), run this command as the root
user:
docker-compose up
.
The first time you run this command, it will take some time as it downloads all of the appropriate images needed for your controller. Once these images are downloaded, the controller will start and you should see something similar to the output below:
Creating network "unifi_unifi" with driver "macvlan"
Creating unifi-controller ... done
Attaching to unifi-controller
unifi-controller | [s6-init] making user provided files available at /var/run/s6/etc...exited 0.
unifi-controller | [s6-init] ensuring user provided files have correct perms...exited 0.
unifi-controller | [fix-attrs.d] applying ownership & permissions fixes...
unifi-controller | [fix-attrs.d] done.
unifi-controller | [cont-init.d] executing container initialization scripts...
unifi-controller | [cont-init.d] 01-envfile: executing...
unifi-controller | [cont-init.d] 01-envfile: exited 0.
unifi-controller | [cont-init.d] 10-adduser: executing...
unifi-controller |
unifi-controller | -------------------------------------
unifi-controller | _ ()
unifi-controller | | | ___ _ __
unifi-controller | | | / __| | | / \
unifi-controller | | | \__ \ | | | () |
unifi-controller | |_| |___/ |_| \__/
unifi-controller |
unifi-controller |
unifi-controller | Brought to you by linuxserver.io
unifi-controller | -------------------------------------
unifi-controller |
unifi-controller | To support LSIO projects visit:
unifi-controller | https://www.linuxserver.io/donate/
unifi-controller | -------------------------------------
unifi-controller | GID/UID
unifi-controller | -------------------------------------
unifi-controller |
unifi-controller | User uid: 1502
unifi-controller | User gid: 1502
unifi-controller | -------------------------------------
unifi-controller |
unifi-controller | [cont-init.d] 10-adduser: exited 0.
unifi-controller | [cont-init.d] 20-config: executing...
It’s important to note that this container takes a bit of time to start (on my system, around 2-3 minutes regularly). You should see this message appear once the container is almost ready:
unifi-controller | [services.d] starting services
unifi-controller | [services.d] done.
It seems like there’s still about 1 minute or less needed for services to start properly within the container so you may have to be patient during this process. Once the controller is ready, you should now be able to browse to it on your network:
https://<ipv4_address>:8443/
So, for our examples, I would use https://192.168.0.8:8443
to access my container. We don’t actually want to start using the system because we have our console attached. So we want to terminate the container (safely) using Ctrl+C
and wait for it to end.
Now, in order you start the controller container and have it run in the background, execute this command:
docker-compose up -d
This will detach the controller process and run it in the background (but there’s still a delay in the container being ready for you to access).
Summary
After following everything in this article, you should now have a UniFi controller running inside of a Docker container. You can now enjoy the benefits of easy maintenance moving forward and easy portability if you need to move your controller in the future. Thanks for your time!