How to Run the UniFi Network Controller in Docker (Manage Your Access Points Without a Cloud Key)

The UniFi controller on your server: adopt APs and switches, VLAN-tagged SSIDs for the IoT network, nightly backups — no Cloud Key, no cloud account.

Ubiquiti's access points and switches are a popular step up from the ISP router — and they want a controller to configure them: a Cloud Key (£100+), a Dream Machine, or Ubiquiti's cloud. Or the UniFi Network Application in a container on the server you already run. It manages APs, switches and VLANs (143), draws the topology, and once set up the devices keep working even if the controller is off.

Quick reference

PieceWhat
Containerslscr.io/linuxserver/unifi-network-application + mongo
Webhttps://server-ip:8443
AdoptionAPs on the same LAN find it via L2; or Set inform to http://server-ip:8080/inform
Ports8080/tcp (inform), 8443/tcp (UI), 3478/udp (STUN), 10001/udp (discovery)
VLAN SSIDSettings → Networks (VLAN-only) → WiFi → network: IoT
BackupSettings → System → Backups → auto; and the ./config folder

1. MongoDB init script

The application needs a user in Mongo created on first start:

mkdir -p ~/docker/unifi/{config,db} && cd ~/docker/unifi
nano init-mongo.js

db.getSiblingDB("unifi").createUser({ user: "unifi", pwd: "CHANGE_ME", roles: [{ role: "dbOwner", db: "unifi" }] });
db.getSiblingDB("unifi_stat").createUser({ user: "unifi", pwd: "CHANGE_ME", roles: [{ role: "dbOwner", db: "unifi_stat" }] });

(Put the real password in; keep the file chmod 600.)

2. The compose file

nano compose.yaml

services:
unifi-db:
image: mongo:7
container_name: unifi-db
volumes:
- ./db:/data/db
- ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
restart: unless-stopped

unifi:
image: lscr.io/linuxserver/unifi-network-application:latest
container_name: unifi
environment:
PUID: 1000
PGID: 1000
TZ: Europe/London
MONGO_USER: unifi
MONGO_PASS: ${MONGO_PASS}
MONGO_HOST: unifi-db
MONGO_PORT: 27017
MONGO_DBNAME: unifi
MEM_LIMIT: 1024
volumes:
- ./config:/config
ports:
- "8443:8443"
- "8080:8080"
- "3478:3478/udp"
- "10001:10001/udp"
- "1900:1900/udp"
depends_on: [unifi-db]
restart: unless-stopped

Port 8080 clashes with FreshRSS (89) if you used it — move one. (Host networking is an alternative that makes L2 discovery just work.)

docker compose up -d
sudo ufw allow from 192.168.1.0/24 to any port 8443 proto tcp
sudo ufw allow 8080/tcp && sudo ufw allow 3478/udp && sudo ufw allow 10001/udp

3. First run

https://192.168.1.50:8443 (self-signed warning). Name the site, skip the Ubiquiti account sign-in (choose "advanced setup" / local account), create a local admin, pick the devices it has already discovered, finish.

4. Adopt the devices

Plug in an AP (PoE) on the LAN. It appears under Devices as Pending adoption → Adopt. If it doesn't appear (different VLAN, Docker bridge not seeing L2 discovery), tell it where the controller is: SSH to the AP (ubnt/ubnt on a new one) and

set-inform http://192.168.1.50:8080/inform

then adopt in the UI. Or set the DHCP option 43 / a DNS record unifi → the server so every new device finds it. Switches the same way.

5. Networks and Wi-Fi

Settings → Networks → Create: IoT, VLAN ID 20, VLAN-only (the router/firewall does the routing and DHCP — tutorial 143). Then WiFi → Create: Home-IoT, network IoT, 2.4 GHz only, and a separate Home on the default network. Trunk the AP's switch port with both VLANs. Guest network with client isolation for visitors. Radios: fixed channels (1/6/11 on 2.4 GHz, non-overlapping 80 MHz on 5 GHz) beat auto in a crowded street.

6. Back up and update

Settings → System → Backups → Auto backup weekly; the .unf files land in ./config/data/backup/autobackup/, which your nightly backup (23) already covers along with the Mongo volume. Updates: docker compose pull && up -d — read the LinuxServer notes when the UniFi major version changes (Mongo version requirements shift). If the controller dies, a fresh one restores from a .unf in a minute and re-adopts everything.

7. Remote management

Reach it over the VPN (33, 47). Don't port-forward 8443; if you want the Ubiquiti phone app from outside, their cloud "remote access" is the trade-off — or just use WireGuard on the phone.

Troubleshooting

Devices stuck "Adopting" / "Disconnected". The inform URL points at the wrong IP, or 8080 isn't reachable from the device's VLAN. set-inform again; check the firewall from that VLAN.

Controller won't start: Mongo errors. The init script didn't run (the ./db folder existed before first start) — stop, delete ./db, start again. Or a Mongo version mismatch after an update.

"Pending adoption" never appears. L2 discovery blocked by Docker bridge: use set-inform, or network_mode: host.

Slow UI. MEM_LIMIT too low; it's a Java app — give it 1–2 GB.

Wrapping up

Two containers, a Mongo init script, a local admin instead of a cloud account, set-inform for stubborn devices, VLAN-only networks for the IoT SSID, weekly backups in a folder you already protect. The access points get a proper controller, and Ubiquiti's cloud never enters the picture.

Call us