Guide to installing the new Unifi Network Application on Portainer

Introduction: With the upcoming deprecation of the old UniFi Controller in 2024, it’s essential to transition to the newer version of the UniFi Network Application. This post guides you through the process using Docker and Portainer, ensuring a smooth upgrade.

Prerequisites:

  • Docker installed on your system
  • Basic knowledge of Docker and Docker Compose
  • Access to Portainer UI

Upgrade

You will need to backup the old unifi controller and restore in the new one.

Setup Steps:

  1. Generate init-mongo.js: Start by running the script to create the MongoDB user for UniFi. Open your terminal and execute:
bash -c "$(wget -qLO - https://raw.githubusercontent.com/bigbeartechworld/big-bear-scripts/master/generate-unifi-network-application-init-mongo/run.sh)"

Follow the prompts provided by the script.

  1. Prepare Docker Compose: Download the Docker Compose file from this GitHub repository. This file defines the UniFi Controller and MongoDB services.

  2. Deploy on Portainer:

  • Go to Stacks > Add Stack in Portainer.
  • Name your stack (e.g., unifi-stack).
  • Paste the Docker Compose content into the Editor.
  • Click on “Deploy the stack” to start the services.

Screenshot 2023-10-29 at 11.47.08 AM

  1. Deploying the UniFi stack on Portainer
  2. Initial Configuration: Once the stack is deployed, you might need to restart the UniFi container for it to connect successfully to MongoDB. After a few minutes, access the UniFi setup wizard at https://[YOUR_PORTAINER_IP]:8443 and follow the on-screen instructions to complete the setup.

Conclusion: You’ve now successfully set up the new UniFi Network Application on Docker using Portainer.

Thanks for this. Just competed installation on my RP4.
This involved changing the Mongodb version to 4.4.18, as later versions don’t work on the Pi’s ARM architecture.
I also found a bug in the script for creating init-mongo.js
At Add generate-unifi-network-application-init-mongo · bigbeartechworld/big-bear-scripts@84c284c · GitHub you define “location” to be the full pathname of init-mongo.js on line 8. But at line 21 you use it to create a directory init-mongo.js. This prevents the init file being created
The I got stuck until i found that Mongodb only reads the init file on first start up - it’s ignored on subsequent restarts

You’re welcome, and I’m glad you got it working. :slight_smile:

Yes, it’s because, as of January 2023, MongoDB 5.0+ requires ARM v8.2-A or later, while the Raspberry Pi 4 uses an ARM Cortex-A72, which is ARM v8-A. This means that the pre-built packages for MongoDB 5.0+ will not support the Raspberry Pi 4. However, you can build from source by adding CCFLAGS-march=armv8 to the SCons invocation.

I don’t see a bug.

mkdir -p "$(dirname "$location")"

dirname Command : The dirname command is used to strip the non-directory portion from a file path. It returns only the directory path of the given file path. For example, if $location is /DATA/AppData/unifi-network-application/db/init-mongo.js , then dirname "$location" will return /DATA/AppData/unifi-network-application/db . This is the directory path where the file init-mongo.js should be located.

mkdir -p Command: The mkdir command is used to create directories. The -p (parents) option tells mkdir to create the directory specified by the given path, and also to create any necessary parent directories along the path that do not already exist. If the directories already exist, mkdir -p will not produce an error, which makes this command safe for ensuring that a directory path exists.

  • Without the -p option, if any part of the specified path does not exist, mkdir would fail with an error.
  • With the -p option, it ensures the entire directory path exists, creating any missing directories along the way.

Putting it together, mkdir -p "$(dirname "$location")" creates the directory where the file specified by $location is supposed to be saved, including any necessary parent directories. This ensures that when you later try to save a file at $location, the directory where the file needs to go already exists, preventing errors related to non-existent directories.

I also found a repo for MongoDB on raspberry pi I haven’t tried them, though.

Before installing anything, make sure you look over the source code.

Hi,

I wondered if you’re able to help me, I’ve got the following log error on the unifi container when trying to start up:

17:07:32,867 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
17:07:32,873 |-INFO in ch.qos.logback.core.rolling.RollingFileAppender[hotspot_log] - Active log file name: logs/hotspot.log
17:07:32,873 |-INFO in ch.qos.logback.core.rolling.RollingFileAppender[hotspot_log] - File property is set to [logs/hotspot.log]
17:07:32,874 |-ERROR in ch.qos.logback.core.rolling.RollingFileAppender[hotspot_log] - openFile(logs/hotspot.log,true) call failed. java.io.FileNotFoundException: logs/hotspot.log (Permission denied)
	at java.io.FileNotFoundException: logs/hotspot.log (Permission denied)
	at 	at java.base/java.io.FileOutputStream.open0(Native Method)
	at 	at java.base/java.io.FileOutputStream.open(FileOutputStream.java:293)
	at 	at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:235)
	at 	at ch.qos.logback.core.recovery.ResilientFileOutputStream.<init>(ResilientFileOutputStream.java:26)
	at 	at ch.qos.logback.core.FileAppender.openFile(FileAppender.java:204)
	at 	at ch.qos.logback.core.FileAppender.start(FileAppender.java:127)
	at 	at ch.qos.logback.core.rolling.RollingFileAppender.start(RollingFileAppender.java:100)
	at 	at ch.qos.logback.core.joran.action.AppenderAction.end(AppenderAction.java:90)
	at 	at ch.qos.logback.core.joran.spi.Interpreter.callEndAction(Interpreter.java:309)
	at 	at ch.qos.logback.core.joran.spi.Interpreter.endElement(Interpreter.java:193)
	at 	at ch.qos.logback.core.joran.spi.Interpreter.endElement(Interpreter.java:179)
	at 	at ch.qos.logback.core.joran.spi.EventPlayer.play(EventPlayer.java:62)
	at 	at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:165)
	at 	at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:152)
	at 	at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:110)
	at 	at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:53)
	at 	at ch.qos.logback.classic.util.ContextInitializer.configureByResource(ContextInitializer.java:64)
	at 	at ch.qos.logback.classic.util.ContextInitializer.autoConfig(ContextInitializer.java:134)
	at 	at org.slf4j.impl.StaticLoggerBinder.init(StaticLoggerBinder.java:84)
	at 	at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:55)
	at 	at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
	at 	at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
	at 	at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
	at 	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
	at 	at com.ubnt.service.system.floatString.Ø00000(Unknown Source)
	at 	at com.ubnt.service.system.floatString.<clinit>(Unknown Source)
	at 	at com.ubnt.ace.Launcher.<clinit>(Unknown Source)
17:07:32,894 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [com.ubnt.ace.logs.InMemoryAppender]
17:07:32,902 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [InMemoryAppender]
17:07:32,909 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
17:07:32,917 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [com.ubnt.service.trace.logerror.AnalyticsAppender]
17:07:32,970 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [AnalyticsAppender]
17:07:34,680 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [java] to ERROR
17:07:34,680 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [javax] to ERROR
17:07:34,680 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [javax.jmdns] to OFF
17:07:34,681 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [sun] to ERROR
17:07:34,681 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [org.apache] to WARN
17:07:34,681 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [httpclient.wire] to WARN
17:07:34,681 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [net.schmizz] to ERROR
17:07:34,681 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [com.codahale] to ERROR
17:07:34,681 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [org.apache.tomcat] to ERROR
17:07:34,682 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [org.apache.commons] to WARN
17:07:34,682 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [org.apache.catalina] to ERROR
17:07:34,682 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [org.hibernate.validator] to WARN
17:07:34,682 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [com.mongodb] to ERROR
17:07:34,682 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [org.mongodb] to ERROR
17:07:34,682 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [org.springframework] to WARN
17:07:34,682 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [de.javawi.jstun] to WARN
17:07:34,683 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [com.ubnt] to INFO
17:07:34,683 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [com.ubiquiti] to INFO
17:07:34,683 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [com.amazonaws.internal] to WARN
17:07:34,683 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [springfox] to WARN
17:07:34,683 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [migration_log] to Logger[migration]
17:07:34,686 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [tasks_log] to Logger[tasks]
17:07:34,686 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [hotspot_log] to Logger[guest]
17:07:34,686 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to INFO
17:07:34,687 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [server_log] to Logger[ROOT]
17:07:34,687 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [InMemoryAppender] to Logger[ROOT]
17:07:34,687 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [AnalyticsAppender] to Logger[ROOT]
17:07:34,687 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
17:07:34,689 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@2af004b - Registering current configuration as safe fallback point
Exception in thread "launcher" com.ubnt.net.C: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].StandardContext[]]
	at com.ubnt.net.OoOO.ÓÒ0000(Unknown Source)
	at com.ubnt.net.OoOO.ÕÒ0000(Unknown Source)
	at com.ubnt.service.C.øÓ0000(Unknown Source)
	at com.ubnt.ace.Launcher.String(Unknown Source)
	at com.ubnt.ace.Launcher.main(Unknown Source)
Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].StandardContext[]]
	at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:440)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:198)
	... 5 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dbService' defined in com.ubnt.service.DatabaseSpringContext: Invocation of init method failed; nested exception is java.lang.NullPointerException: Cannot read the array length because "<local3>" is null
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
	at com.ubnt.service.E.Object(Unknown Source)
	at com.ubnt.service.C.oo0000(Unknown Source)
	at com.ubnt.net.SpringConfig.onStartup(Unknown Source)
	at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:174)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5219)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
	... 5 more
Caused by: java.lang.NullPointerException: Cannot read the array length because "<local3>" is null
	at com.ubnt.service.system.o000OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.afterPropertiesSet(Unknown Source)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800)

	... 20 more

      

I’ve tried binning it all off and trying again, but not had any luck with trying to get past those errors. Any advice you can share with me please? - Running on a fresh portainer on a pi4 (4gb) and only other working stack is pi-hole.

My current build code is:

---
version: "2.1"
services:
  unifi-network-application:
    image: lscr.io/linuxserver/unifi-network-application:latest
    container_name: unifi-network-application
    depends_on:
      - unifi-db
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
      - MONGO_USER=unifi
      - MONGO_PASS=unifi
      - MONGO_HOST=unifi-db
      - MONGO_PORT=27017
      - MONGO_DBNAME=unifi-db
      - MEM_LIMIT=1024 #optional
      - MEM_STARTUP=1024 #optional
      - MONGO_TLS= #optional
      - MONGO_AUTHSOURCE= #optional
    volumes:
      - /path/to/data:/config
    ports:
      - 8443:8443
      - 3478:3478/udp
      - 10001:10001/udp
      - 7080:8080
      - 1900:1900/udp #optional
      - 8843:8843 #optional
      - 8880:8880 #optional
      - 6789:6789 #optional
      - 5514:5514/udp #optional
    restart: unless-stopped
 
  unifi-db:
    image: docker.io/mongo:4.4.6
    container_name: unifi-db
    # Extra stuff from the web
    ports:
      - 27017:27017
    expose:
      - 27017
    environment:
      - MONGO_INITDB_DATABASE=unifi-db
    volumes:
      - /path/to/data:/data/db
      - /home/Matt/Downloads/docker/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
    restart: unless-stopped

Thanks in advance

Hi
Thanks for the video.

I have a Unifi Cloud Key that has crashed I get the 404 error when I try to log into the Unifi Controller but can still log into the Cloud Key ok.

I am wanting to set up Unifi Application as described in your video but on my Ubuntu server on a Docker Container using Portainer.

I have the last ten backup for the could key. As part of the setup for the Unifi Application can I restore from the Cloud Keys backup?

Thanks

You should be able to restore. I’ve restored from a backup before. You might need to be on the same version as your cloud key, though.

Hello. Thanks for the guide. When deploying in Ubuntu 24.04 running as a VM in Proxmox, I get a strange issue. Everything works great for some hours, until the Unifi app starts showing “username or password is incorrect”. I tried different things. Tried using other versions of Ubuntu. Completely changed to a different server with Proxmox 8.2.2 (the first one was 6.4). Used latest version of MongoDB, etc etc. The same happens: Everything runs smooth for some hours until, Bam!: Username or password is incorrect!

Did anyone else report the same issue?