Docker image for TP-Link Omada Software Controller (also known as TP-Link Omada Network Application) to centrally manage TP-Link Omada Hardware.
[!NOTE] Disclaimer: I am not, in any way, affiliated with TP-Link. I am just a community member who has packaged TP-Link's free software as a Docker image for easier management and consumption.
For instructions on running a legacy v3 or v4 controller, see the README for v3 and v4. See the tag archive_v3_v4 for a snapshot of the code that includes the v3 and v4 artifacts as they have been removed as of July 2, 2024.
If you have issues running the controller, feel free to create a Help discussion and I will help as I can. If you are specifically having a problem that is related to the actual software, I would suggest filing an issue on the TP-Link community forums or contacting TP-Link's support team as I do not have access to source code to debug those issues. If you're not sure where the problem might be, I can help determine if it is a running in Docker issue or a software issue. If you're certain you have found a bug, create a Bug Report Issue.
If you don't know much about Docker or want to just get started as easily as possible, start here as this will guide you through this readme on key concepts and things you want to take into consideration.
major version tagged - For people who want to set it and forget it (i.e. - if you are used to just using latest), there is also the major tag version (i.e. - 5, 6, etc) which should be mostly safe from non-breaking changes.major.minor version tagged - Most people will want to use a major.minor tag version (i.e. - 6.3) as this is the safest option and can almost certainly be considered to be non-breaking when a new version of the image is available.latest TAG IS A BAD IDEA - DO NOT DO IT! Using latest with software like this is not a good idea because you may upgrade to a version that your devices do not support and TP-Link can put in breaking changes at any time! Instead, use one of the two tag types above.amd64 so only use them if you really need that functionality.6.3) tag6.3) taghttps://<ip-address-or-hostname>:8043/, assuming you're using the default ports.There are three main options for upgrading from v5 to v6:
This upgrade guide will focus on using the MongoDB upgrade container. The native controller migration and backup & restore procedures are using built in controller capabilities so you should follow TP-Link documentation but those details are out of scope of this guide.
[!IMPORTANT] The upgrade requires a manual step of a MongoDB upgrade which is automated but it has to be run as a separate container while the controller is stopped.
There are a few reasons for the manual upgrade:
Now that you understand why there is manual step required, see the MongoDB upgrade guide for detailed instructions on how to proceed with the upgrade.
If you tried to run the v6 controller without doing the upgrade, see HELP! My Controller Stopped Working! for steps to get back to a working state or what you need to do to get back to a state where you can perform the upgrade.
For every version at any given point in time, there are many different tags: major, major.minor, and a full major.minor.patch.build tag. For guidance on what image tag you should use, check out the Picking an image tag section of the quickstart guide above but the gist of it is that most people will want to use either a major or major.minor version tag.
[!IMPORTANT] The
latesttag still points to the latest v5 version of the software even thought v6 is available. This will not change in the future as I am deprecating the use of thelatesttag due to the inability for me to perform a migration for v5 to v6 without manual user intervention and not wanting to break anyone's controller who is using an auto-update mechanism. Review the quickstart guide on tag versions to understand the tagging methodology or review the version tables below.
For a full tag list, search the Docker Hub tags list. The following tags have multi-arch support for amd64 and arm64 and will automatically pull the correct tag based on your system's architecture.
These are builds with the standard OpenJDK JVM.
| Tag(s) | Major.Minor Release | Current Version |
|---|---|---|
6, 6.3 | 6.3.x | 6.3.0.45 |
6.2 | 6.2.x | 6.2.14.11 |
These are builds with OpenJ9; an alternative to the standard OpenJDK JVM implementation. OpenJ9 is optimized for low memory footprint and fast startup time.
[!NOTE] Work on optimizing the OpenJ9 builds is still ongoing but you will likely see a reduced memory footprint so these builds are ideal for situations where memory usage is more of a concern.
| Tag(s) | Major.Minor Release | Current Version |
|---|---|---|
6-openj9, 6.3-openj9, 6.3.0.45-openj9 | 6.3.x | 6.3.0.45 |
6.2-openj9, 6.2.14.11-openj9 | 6.2.x | 6.2.14.11 |
If for some reason you can't use the multi-arch tags, there are explicitly tagged images with the architecture (-amd64 and -arm64) appended to them. Check Docker Hub for the full list of tags.
If you need a specific version of the controller, starting with 5.13 and 5.14, there are explicitly tagged images with the exact version (i.e. - 6.3.0.45) in the tag name. Check Docker Hub for the full list of tags.
These are multi-arch tags. For the full tag listings, see the Docker Hub tags above but the general format for beta-* and *-openj9 follow for the specific architecture tags. OpenJ9 images are only available for amd64 and arm64.
| Tag(s) | Major.Minor Release | Current Version |
|---|---|---|
beta, beta-6.3, beta-6.3.0.111 | 6.3.x Beta | 6.3.0.111 |
beta-openj9, beta-6.3-openj9, beta-6.3.0.111-openj9 | 6.3.x Beta w/OpenJ9 | 6.3.0.111 |
[!WARNING] Do NOT run the
armv7l(32 bit) images. Upgrade your operating system toarm64(64 bit) unless you accept that you're running an outdated MongoDB, a base operating system with unpatched vulnerabilities, an old version of Java, and a controller that will never be upgraded beyond5.15.8.2! See the Known Issues section for more information.
These images are still published on Docker Hub but are no longer regularly updated due to the controller software no longer being updated. Use with extreme caution as these images are likely to contain unpatched security vulnerabilities!. See Archived Tags for v3 and v4 for details on the old, unmaintained image tags.
| Tag(s) | Major.Minor Release | Current Version |
|---|---|---|
6.1 | 6.1.x | 6.1.0.19 |
6.1-openj9, 6.1.0.19-openj9 | 6.1.x | 6.1.0.19 |
6.0 | 6.0.x | 6.0.0.25 |
6.0-openj9, 6.0.0.25-openj9 | 6.0.x | 6.0.0.25 |
latest, 5, 5.15 | 5.15.x | 5.15.24.19 |
5.15-openj9, 5.15.24.19-openj9 | 5.15.x | 5.15.24.19 |
5.14 | 5.14.x | 5.14.32.4 |
5.14-openj9, 5.14.32.4-openj9 | 5.14.x w/OpenJ9 | 5.14.32.4 |
5.13 | 5.13.x | 5.13.30.8 |
5.13-chromium | 5.13.x | 5.13.30.8 |
5.13-openj9, 5.13.30.8-openj9 | 5.13.x w/OpenJ9 | 5.13.30.8 |
5.12 | 5.12.x | 5.12.7 |
5.12-chromium | 5.12.x | 5.12.7 |
5.9 | 5.9.x | 5.9.31 |
5.9-chromium | 5.9.x | 5.9.31 |
5.8 | 5.8.x | 5.8.4 |
5.8-chromium | 5.8.x | 5.8.4 |
5.7 | 5.7.x | 5.7.4 |
5.7-chromium | 5.7.x | 5.7.4 |
5.6 | 5.6.x | 5.6.3 |
5.6-chromium | 5.6.x | 5.6.3 |
5.5 | 5.5.x | 5.5.6 |
5.5-chromium | 5.5.x | 5.5.6 |
5.4 | 5.4.x | 5.4.6 |
5.4-chromium | 5.4.x | 5.4.6 |
5.3 | 5.3.x | 5.3.1 |
5.3-chromium | 5.3.x | 5.3.1 |
5.1 | 5.1.x | 5.1.7 |
5.1-chromium | 5.1.x | 5.1.7 |
5.0 | 5.0.x | 5.0.30 |
Going forward, Chromium is no longer required as of 5.14. If you were using a Chromium tag, go back to a normal tag. All reports should now either by CSV or XLSX format.
In order to ensure that the container does not attempt to use excessive amounts of CPU or memory, it is strongly suggested that you set resource constraints on your container. The examples do not specifically have those set as the values you set may depend on your system's available resources. A safe starting point, which I personally use, is the docker run... equivalent of --cpus 2 --memory 4g --memory-swap 5g. Adapt this for whatever method you use whether it's docker run..., docker compose, k8s manifests, or the Helm chart.
If you wish to modify the default JVM arguments of -Xms128m -Xmx1024m to something else, you can do so by utilizing the _JAVA_OPTIONS environment variable. See the Low Resource Systems section of the known issues for more details.
While you can take backups of your controller by making a copy of the persistent data, the chance of data corruption exists if you do so while the container is running as there is a database used for persistence. The best way to take backups is to use the automatic backup capabilities within the controller itself. Go to Settings > Maintenance > Backup and scroll down to Auto Backup to enable and configure the feature. These backups can be restored as a part of the installation process on a clean controller install. If you do not see Settings > Maintenance, you may be drilled down into a sites' configuration. Make sure you're in the Global view as settings that impact the controller as a whole, like backups, are in that Global view.
Backups can also be taken manually on the same screen as the auto backup settings. This would be ideal to do before you perform an upgrade to ensure that you are able to roll back in case of issues upon upgrade as you can not move from a newer version of the controller to an older version! It will break the database and require you to do a full reinstall!
If you do want to just take a snapshot of your persistent data, make sure you stop the container cleanly, tar/zip/snapshot the data in some way, and then start the container back up to bring the controller back online.
Before performing any upgrade, I would suggest taking a backup through the controller itself. Controller upgrades are done by stopping the existing container gracefully (see the note below on this topic), removing the existing container, and running a new container with the new version of the controller. This can be done manually, with compose, or with many other 3rd party tools which auto-update containers, such as Watchtower.
When stopping your container in order to upgrade the controller, make sure to allow the MongoDB enough time to safely shutdown. This is done using docker stop -t <value> where <value> is a number in seconds, such as 60, which should allow the controller to cleanly shutdown. Database corruption has been observed when not cleanly shut down. The docker run and compose examples now include --stop-timeout and stop_grace_period which are set to 60s.
There are pre-built images on Docker Hub - you only need to build your own image if you either don't trust pre-built images or want to do something custom to the image.
There are some differences between the build steps for amd64, arm64, and armv7l. These changes will happen automatically if you use the build-args INSTALL_VER and ARCH. For possible INSTALL_VER values, see mbentley/docker-omada-controller-url:
amd64No build args required; set for the default build-args
docker build \
--build-arg BASE=mbentley/ubuntu:24.04 \
--build-arg INSTALL_VER="6.3.0.45" \
--build-arg ARCH="amd64" \
-f Dockerfile \
-t mbentley/omada-controller:6.3-amd64 .
arm64Only the ARCH build-arg is required
docker build \
--build-arg BASE=mbentley/ubuntu:24.04 \
--build-arg INSTALL_VER="6.3.0.45" \
--build-arg ARCH="arm64" \
-f Dockerfile \
-t mbentley/omada-controller:6.3-arm64 .
armv7l[!WARNING] The
armv7lversion was deprecated and support has been removed for versions beyond5.15.8.2.
Both the ARCH and BASE build-args are required
docker build \
--build-arg INSTALL_VER="5.15.8.2" \
--build-arg ARCH="armv7l" \
--build-arg BASE="ubuntu:16.04" \
-f Dockerfile \
-t mbentley/omada-controller:5.15-armv7l .
[!NOTE] Regardless of how you run the container, I would strongly recommend adding CPU and memory limits for the container. See the section on Container Resource Constraints in the Best Practices for Operation section of the documentation.
These example below are based on docker run... commands. See Using Docker Compose for compose examples or Using k8s for example k8s manifests. See Optional Environment Variables for details on the environment variables that can modify the behavior of the controller inside the container. To run this Docker image and keep persistent data in named volumes:
net=hostUsing host networking mode is the preferred method of running the controller. In order to use the host's network namespace, you must first ensure that there are not any port conflicts. The docker run command is the same except for that all of the published ports should be removed and --net host should be added. Technically it will still work if you have the ports included, but Docker will just silently drop them. Here is a snippet of what the above should be modified to look like:
docker run -d \
--name omada-controller \
--stop-timeout 60 \
--restart unless-stopped \
--ulimit nofile=4096:8192 \
--net host \
-e TZ=Etc/UTC \
-v omada-data:/opt/tplink/EAPController/data \
-v omada-logs:/opt/tplink/EAPController/logs \
mbentley/omada-controller:6.3
When is comes to device management, using port mapping is more complex than using host networking as your devices need to be informed of the controller's IP or hostname. For instructions on how to configure your device for adoption, see the device adoption readme. If you do not follow these instructions, it is highly likely that new devices will fail to adopt!
[!WARNING] If you want to change the controller ports from the default mappings, you absolutely must update the port binding inside the container via the environment variables. The ports exposed must match what is inside the container. The Omada Controller software expects that the ports are the same inside the container and outside and will load a blank page if that is not done. See #99 for details and and example of the behavior.
docker run -d \
--name omada-controller \
--stop-timeout 60 \
--restart unless-stopped \
--ulimit nofile=4096:8192 \
-p 8088:8088 \
-p 8043:8043 \
-p 8044:8044 \
-p 8843:8843 \
-p 19810:19810/udp \
-p 27001:27001/udp \
-p 29810:29810/udp \
-p 29811-29817:29811-29817 \
-e TZ=Etc/UTC \
-v omada-data:/opt/tplink/EAPController/data \
-v omada-logs:/opt/tplink/EAPController/logs \
mbentley/omada-controller:6.3
[!WARNING] In most cases, overriding the default port numbers using environment variables mostly ONLY works the first time the container is started by default. After the database is initialized, the controller app reads the port configuration from the database and any modification of the ports through the controller's web interface will also be persisted to the database as well. If you wish to modify the ports using the environment variables on subsequent container startups, you must also set the
WEB_CONFIG_OVERRIDEenvironment variable totrueand they'll be re-read on next startup.
There are two sets of ports for the main controller (not including the specific ports used for a variety of management functions; see the Optional Environment Variables section for full details on those):
MANAGE_*_PORT environment variables, can only be modified using the environment variables on the first time the controller is started. If persistent data exists, changing the controller's ports via environment variables will have no effect on the controller itself and can only be modified through the web UI.PORTAL_*_PORT environment variables.[!TIP] If you make changes to the ports via the web UI, you should update the environment variables to match as well, just to be safe as there are some odd quirks to how the ports are read.
There is an optional ability to run the container in a rootless mode. This version has fewer pre-flight capabilities to do tasks like set permissions for you but works in environments where running containers as root is blocked (i.e. - many Kubernetes environments). To activate the rootless entrypoint the following conditions must be met:
ROOTLESS to truePUID and PGID variables do not apply heredata and logs/certs path when injecting your own certificates, must be readable by the user in which you're running asThere are a few Docker Compose files available that can serve as a guide if you want to use compose to managed the lifecycle of your container. Depending on which network mode of operation you want to use, there are example for each: host networking or bridge/port mapping.
<download the compose file you wish to use>
<edit the compose file to match your persistent data needs>
docker compose up -d
There are two available options for deployment to Kubernetes:
The Helm charts are published to mbentley/omada-controller-helm on Docker Hub but are also available directly from this repo. For a Helm release list and detailed usage instructions, check out the Helm chart's README.
Examples of how to deploy a complete stack of MongoDB and Omada Controller using Helm can be found in the k8s/helm directory. Follow the README to get started. Review the values files to see how to configure the chart for your environment.
The example manifests are in the k8s/manifests directory. It's assumed that you will know how to modify and use these manifests on k8s if you choose that as your deployment option.
| Variable | Default | Values | Description | Valid For |
|---|---|---|---|---|
_JAVA_OPTIONS | null | any valid JVM args | Overrides the default JVM memory arguments (e.g. -Xms128m -Xmx1024m); takes priority over CLI args without modifying them; see Low Resource Systems for more details | >= 3.2 |
APPLICATION_PROPERTIES | null | any Spring Boot properties | Content written verbatim to application.properties on the classpath at startup, loaded by Spring Boot; see Spring Boot Tuning for details and recommended settings | >= 5.x |
EAP_MONGOD_URI | null | mongodb://user:pass@1.2.3.4:27017/omada | Used to specify the URI of MongoDB when running it external to the controller container | >= 5.x |
JAVA_MAX_HEAP_SIZE | null | any valid JVM heap size | Replaces the hardcoded -Xmx value in the default CMD (e.g. 512m, 1g); works with any JVM including HotSpot | >= 3.2 |
JAVA_MIN_HEAP_SIZE | null | any valid JVM heap size | Replaces the hardcoded -Xms value in the default CMD (e.g. 64m, 128m); works with any JVM including HotSpot | >= 3.2 |
MANAGE_HTTP_PORT | 8088 | 1024-65535 | Management portal HTTP port; for ports < 1024, see Unprivileged Ports | >= 3.2 |
MANAGE_HTTPS_PORT | 8043 | 1024-65535 | Management portal HTTPS port; for ports < 1024, see Unprivileged Ports | >= 3.2 |
MONGO_EXTERNAL | false | true, false | Disables MongoDB from starting inside the controller container; used for external MongoDB | >= 5.x |
MONGOD_EXTRA_ARGS | null | any valid mongod args | Appends extra arguments to every embedded mongod invocation (e.g. --wiredTigerCacheSizeGB 0.25); not supported in rootless mode | >= 5.x |
PGID | 508 | any | Set the omada process group ID ` | >= 3.2 |
PGROUP | omada | any | Set the group name for the process group ID to run as | >= 5.0 |
PORTAL_HTTP_PORT | 8088 | 1024-65535 | User portal HTTP port; for ports < 1024, see Unprivileged Ports | >= 4.1 |
PORTAL_HTTPS_PORT | 8843 | 1024-65535 | User portal HTTPS port; for ports < 1024, see Unprivileged Ports | >= 4.1 |
PORT_APP_DISCOVERY | 27001 | 1024-65535 | Omada Controller can be discovered by the Omada APP within the same network through this port | >= 5.x |
PORT_DISCOVERY | 29810 | 1024-65535 | Omada Controller and Omada Discovery Utility discover Omada devices | >= 5.x |
PORT_MANAGER_V1 | 29811 | 1024-65535 | Omada Controller and Omada Discovery Utility manage the Omada devices running firmware fully adapted to Omada Controller v4* | >= 5.x |
PORT_ADOPT_V1 | 29812 | 1024-65535 | Omada Controller and Omada Discovery Utility manage the Omada devices running firmware fully adapted to Omada Controller v4* | >= 5.x |
PORT_UPGRADE_V1 | 29813 | 1024-65535 | When upgrading the firmware for the Omada devices running firmware fully adapted to Omada Controller v4*. | >= 5.x |
PORT_MANAGER_V2 | 29814 | 1024-65535 | Omada Controller and Omada Discovery Utility manage the Omada devices running firmware fully adapted to Omada Controller v5* | >= 5.x |
PORT_TRANSFER_V2 | 29815 | 1024-65535 | Omada Controller receives Device Info and Packet Capture files from the Omada devices | >= 5.9 |
PORT_RTTY | 29816 | 1024-65535 | Omada Controller establishes the remote control terminal session with the Omada devices | >= 5.9 |
PORT_DEVICE_MONITOR | 29817 | 1024-65535 | Omada Controller ??? (currently unknown the full purpose) | >= 06.0 |
PUID | 508 | any | Set the omada process user ID ` | >= 3.2 |
PUSERNAME | omada | any | Set the username for the process user ID to run as | >= 5.0 |
ROOTLESS | false | true, false | Sets the entrypoint for rootless mode | >= 5.14 |
SHOW_SERVER_LOGS | true | true, false | Outputs Omada Controller logs to STDOUT at runtime | >= 4.1 |
SHOW_MONGODB_LOGS | false | true, false | Outputs MongoDB logs to STDOUT at runtime | >= 4.1 |
SKIP_USERLAND_KERNEL_CHECK | false | true, false | When set to true, skips the userland/kernel match check for armv7l & arm64 | >= 3.2 |
SSL_CERT_NAME | tls.crt | any | Name of the public cert chain mounted to /cert; see Custom SSL Certificates | >= 3.2 |
SSL_KEY_NAME | tls.key | any | Name of the private cert mounted to /cert; see Custom SSL Certificates | >= 3.2 |
TLS_1_11_ENABLED | false | true, false | Re-enables TLS 1.0 & 1.1 if set to true | >= 4.1 |
TZ | Etc/UTC | <many> | See Time Zones for more detail | >= 3.2 |
UPGRADE_ES_HTTPS_PORT | 8044 | 1024-65535 | Dedicated HTTPS port for upgrades for ES series devices | >= 6.3.0.36 |
UPGRADE_HTTPS_PORT | 8043 | 1024-65535 | Dedicated HTTPS port for upgrades, separate from the main Controller port | >= 6.1 |
WEB_CONFIG_OVERRIDE | false | true, false | Forces the controller to re-read port configuration from omada.properties on startup; see Using non-default ports | >= 5.x |
Documentation on the ports used by the controller can be found in the TP-Link FAQ.
In the examples, there are two directories where persistent data is stored: data and logs. The data directory is where the persistent database data is stored where all of your settings, app configuration, etc is stored. The log directory is where logs are written and stored. I would suggest that you use a bind mounted volume for the data directory to ensure that your persistent data is directly under your control and of course take regular backups within the Omada Controller application itself. Previous versions of the controller (before 5.x) also used a work persistent directory omada-work which was mapped to /opt/tplink/EAPController/work inside the container where the application was deployed. This work directory is no longer needed as of 5.0.x.
By default, Omada software uses self-signed certificates. If however you want to use custom certificates you can mount them into the container as /cert/tls.key and /cert/tls.crt. The tls.crt file needs to include the full chain of certificates, i.e. cert, intermediate cert(s) and CA cert. This is compatible with kubernetes TLS secrets. Entrypoint script will convert them into Java Keystore used by jetty inside the Omada SW. If you need to use different file names, you can customize them by passing values for SSL_CERT_NAME and SSL_KEY_NAME as seen above in the Optional Environment Variables section.
[!WARNING] As of the version 4.1, certificates can also be installed through the web UI. You should not attempt to mix certificate management methods as installing certificates via the UI will store the certificates in MongoDB and then the
/certvolume method will cease to function. If you installed certificates using the UI and want to revert this - see this discussion.
By default, this image uses the Etc/UTC time zone. You may update the time zone used by passing a different value in the TZ variable. See List of tz database time zones for a complete list of values in the TZ identifier table column.
This Docker image runs as a non-root user by default. In order to bind unprivileged ports (ports < 1024 by default), you must include --sysctl net.ipv4.ip_unprivileged_port_start=0 in your docker run command to allow ports below 1024 to be bound by non-root users.
If you are interested in using the Omada Controller APIs to retrieve data from the controller, the latest version of the API documentation that I have found is available from the community forums in this post. I'm not able to provide support for the APIs but I've found them to be helpful for my own usage and they weren't easy to find.
Users who are using bridge mode often report that routers, switches, and EAPs fail to adopt. This is due to the controller being technically being on a different network inside the container's bridge network, exporting the ports via NAT. Using port mapping is more complex than using host networking as your devices need to be informed of the controller's IP or hostname. See the device adoption docs for instructions on how to configure this on your device(s) prior to attempting to adopt them.
While MongoDB is fairly robust, the persistent data can become corrupt if a clean shutdown isn't performed. By default, Docker only waits 10 seconds before killing the container processes when using docker stop.... I would highly recommend performing a stop with a large timeout value, such as docker stop -t 30... to ensure that the controller is cleanly shut down. This value may need to be even larger for low powered devices, such as a Raspberry Pi.
armv7l[!WARNING] Deprecation and Removal Notice - armv7l images will no longer be available starting with the v5.15.20 and later versions. See this issue describing the change. The last version that will be available for
armv7lis5.15.8.2.Do not run the Omada Controller on your
armv7l/armhf(32 bit arm) based operating system! If you're running as Raspberry Pi 3, 4, Pi Zero 2W, you should run a 64 bit operating system so you can use thearm64image which is supported. At any time, TP-Link can break compatibility with 32 bit arm and there will be no upgrade path forward! You have been warned!
armv7lAll armv7l images are based on Ubuntu 16.04 due to the lack of packaging for MongoDB in newer Ubuntu releases. Ubuntu 16.04 is end of general support so security patches aren't regularly being released. I would highly recommend not using the armv7l images unless you have no other alternative and accept the security risk. If you are running a Raspberry Pi, I might suggest looking into running an arm64 based operating system if your system supports it (the Raspberry Pi 3 and above do)
The armv7l architecture is 32 bit and MongoDB is no longer available as a pre-compiled binary in Ubuntu, this means that the armv7l images are running version 2.6.10 of MongoDB. This may lead to unexpected behavior as TP-Link states Omada Controller version 4.1.x and newer require at least MongoDB 3.0.15 or newer, depending on which version of the controller you're running. For the armv7l architecture, I will continue to include those in the builds until they stop working as I can't guarantee that an update will not actually require a newer MongoDB feature that isn't available.
Systems such as Raspberry Pis or Kubernetes pods with tight memory limits may not have sufficient memory to run with the default memory settings of this image. The default JVM heap is -Xmx1024m and MongoDB's WiredTiger cache defaults to half of available RAM, which together can push memory usage well above 1.5 GB.
The default CMD includes hardcoded -Xmx1024m -Xms128m. These can already be overridden by JVM-specific environment variables such as _JAVA_OPTIONS, but those do not change the visible command line. If you want the effective heap settings to also be reflected in ps output, you can use JAVA_MAX_HEAP_SIZE and JAVA_MIN_HEAP_SIZE, which replace the existing -Xmx and -Xms arguments in the startup command.
The available approaches are:
Setting JAVA_MAX_HEAP_SIZE (and optionally JAVA_MIN_HEAP_SIZE). This directly replaces the -Xmx (and -Xms) value in the startup command and is visible in ps output. Works with any JVM, including HotSpot and OpenJ9.
JAVA_MAX_HEAP_SIZE=512m
JAVA_MIN_HEAP_SIZE=128m
Setting the _JAVA_OPTIONS environment variable; for example, _JAVA_OPTIONS="-Xms128m -Xmx768m". This already takes priority over the CLI args on HotSpot, but it does not modify the visible command line so tools like ps will still show the original values.
Overriding the CMD as seen in this issue here.
By default MongoDB will use up to half of available RAM for its WiredTiger cache. On a system with 2 GB RAM this means up to 1 GB for MongoDB alone. You can limit it with MONGOD_EXTRA_ARGS:
MONGOD_EXTRA_ARGS=--wiredTigerCacheSizeGB 0.25
[!NOTE]
MONGOD_EXTRA_ARGSis not supported in rootless mode.
If memory footprint is a priority, consider using one of the OpenJ9 image tags (e.g. 6.3-openj9). Eclipse OpenJ9 is optimized for low memory use and fast startup time. In practice it can reduce the Java process RSS by 30-50% compared to HotSpot at the same -Xmx setting due to more aggressive idle GC and shared class caching.
When using an OpenJ9 image you can additionally tune idle behaviour:
OPENJ9_JAVA_OPTIONS=-XX:+IdleTuningGcOnIdle -XX:+IdleTuningCompileAtIdle
[!NOTE]
OPENJ9_JAVA_OPTIONSis appended after the JVM command line in OpenJ9, so it takes precedence over the hardcoded-Xmx1024m. If you also setJAVA_MAX_HEAP_SIZE/JAVA_MIN_HEAP_SIZE, the values should match so the effective heap settings and the visible command line stay consistent.
The following environment variables result in approximately 1–1.3 GB RSS in practice, leaving headroom in a 2 GB pod limit:
# Image tag: mbentley/omada-controller:6.3-openj9
# (or a pinned version such as 6.3.0.45-openj9)
JAVA_MAX_HEAP_SIZE: "512m"
JAVA_MIN_HEAP_SIZE: "128m"
MONGOD_EXTRA_ARGS: "--wiredTigerCacheSizeGB 0.25"
OPENJ9_JAVA_OPTIONS: "-Xmx512m -Xms128m -XX:+IdleTuningGcOnIdle -XX:+IdleTuningCompileAtIdle"
In this example the heap values are intentionally duplicated. On OpenJ9, OPENJ9_JAVA_OPTIONS is authoritative; JAVA_MAX_HEAP_SIZE and JAVA_MIN_HEAP_SIZE are included so the startup command line shown by tools such as ps matches the effective settings. On HotSpot, OPENJ9_JAVA_OPTIONS is ignored and the JAVA_*_HEAP_SIZE values remain effective.
Approximate memory budget:
| Component | Approx. |
|---|---|
Java heap (-Xmx) | 512 MB |
| Java metaspace + JIT code cache | ~150 MB |
| MongoDB WiredTiger cache | 256 MB |
| OS / JVM / MongoDB overhead | ~300 MB |
| Total | ~1.2–1.4 GB |
In one Kubernetes deployment with the embedded MongoDB, the following settings worked reliably on a small network with 1 switch, 3 APs, and about 40 clients:
JAVA_MAX_HEAP_SIZE: "512m"
JAVA_MIN_HEAP_SIZE: "128m"
MALLOC_ARENA_MAX: "2"
MONGOD_EXTRA_ARGS: "--wiredTigerCacheSizeGB 0.25"
OPENJ9_JAVA_OPTIONS: "-Xquickstart -Xcodecachetotal16m -XcompilationThreads1 -Xss192k -Xmso192k -XX:-TransparentHugePage -XX:+IdleTuningGcOnIdle"
This tuning reduced OpenJ9's non-heap overhead significantly (smaller JIT code cache, fewer JIT compiler threads, smaller thread stacks, and disabled transparent huge pages), but the total pod memory still fluctuated around roughly 0.9-1.1 GiB because the container includes both the Java controller and mongod.
Treat this as a practical reference point rather than a universal recommendation. Larger sites, more devices, more clients, firmware upgrades, or long-running background tasks may require more headroom.
Changing these values would be necessary on these low resource systems to prevent the operating system from killing the container due to it thinking it can allocate more memory than it should. The controller process may still actually functionally require more memory so your mileage may vary in terms of the impact of running on such a low resource system.
For advanced tuning of Spring Boot internals (Tomcat thread pool, async executors, logging levels), see Spring Boot Tuning.
If a Raspberry Pi 4 is running a 32 bit version of Raspberry Pi OS, a recent firmware update has intentionally made it so the default kernel the Pi will boot from has been switched from 32 bit kernel to a 64 bit kernel. This is a problem for the running container because the version of MongoDB that is present in the armv7l image (also known as armhf), will fail to start on a 64 bit kernel. Most software tends to run fine when switching kernels but in this case, it will prevent the controller from running due to MongoDB failing to start. Please also review the Notes for armv7l to also understand the risks for running the armv7l based controller!
To fix this issue in the short term, you will want to instruct your Pi to boot from a 32 bit kernel instead of the 64 bit kernel by:
arm_64bit=0 to the /boot/config.txt fileA proper long term solution would be to reinstall Raspberry Pi OS on your Pi 4 and use the new arm64 based operating system which will get you a 64 bit userland and a 64 bit kernel. There are significant known issues while running the armv7l image on a device so longer term, this is the best solution.
It has been reported that a number of users are seeing 404s or blank pages after upgrading to version 5.8 and 6.3. This can be resolved by either force-reloading the page or by clearing your browser's cache.
[!NOTE] Additionally for 6.3 - the login URL has changed from
https://<your-ip-or-hostname>:8043/logintohttps://<your-ip-or-hostname>:8043/independent/index.html#login. Update any bookmarks you may have to the login page.
It has been reported that users of some NAS devices such as a Synology or users of a Docker management UI like Portainer have had issues with upgrades due to the CMD being retained between versions. This normally does not happen with the Docker command line so it is a bit of an unexpected pattern but it can not be overwritten as it exists outside of the container.
If updating from 3.x to 4.x or 4.x to 5.x, make sure to completely re-create the container (leaving your persistent data intact) otherwise the controller will not start. This is due to the CMD changing between the major releases as some web interfaces like Synology or Portainer retain the entrypoint and command explicitly instead of inheriting it from the image. To resolve the issue, do one of the following:
/usr/bin/java -server -Xms128m -Xmx1024m -XX:MaxHeapFreeRatio=60 -XX:MinHeapFreeRatio=30 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tplink/EAPController/logs/java_heapdump.hprof -Djava.awt.headless=true -cp /opt/tplink/EAPController/lib/*::/opt/tplink/EAPController/properties: com.tplink.smb.omada.starter.OmadaLinuxMainIt should be noted that users of 3.x who wish to upgrade to 4.x must perform specific upgrade steps to prevent data loss!
There is a known bug in the controller software where a user is not able to login with their local user after upgrading to 5.12.x. This has been reported to TP-Link but a fix has not yet been provided.
In versions 5.8 to 5.12, it has been seen where Safari will take a significant amount of time to completely load a page in the controller web interface. This is an issue that has been reported upstream.
Upon upgrade to 5.14, the controller may not start. You may see error messages that include phrases like: Cannot retry start up springboot, Unsatisfied dependency expressed through field..., org.springframework.beans.factory.UnsatisfiedDependencyException, among others. This is a problem with the controller software itself that TP-Link is aware of. If you're impacted, see the first post in this issue for possible workaround instructions and more information. This issue should no longer be present on the latest 5.14 versions.
[!CAUTION] Do NOT use this override environment variable unless you need it. It may cause unexpected issues in the future. Remove the environment variable if you're no longer running on 5.15.6.x.
Upon upgrade to 5.15.6.x, the controller may not start. You may see error messages right around the Valid radius server keystore is missing. Generating one ... message that include phrases like: Exception in thread "main" java.lang.NoSuchFieldError: id_alg_zlibCompress among others. This is a problem with the controller software itself that TP-Link is aware of. If you're impacted, see the first post in this issue for more information. An environment variable can be set as WORKAROUND_509=true on the container definition and it will delete two library files that are causing the issue.
In order to run v6 Omada Controller using the images I publish, you will need to be able to run MongoDB 8 but unfortunately MongoDB does not run on older CPUs if they lack specific instructions or features. There are options which will allow you to run v6 of the controller but longer term, I would suggest upgrading your system to support these instruction sets or features required but I understand that means financial investment.
[!TIP] Proxmox users: if your host's CPU supports AVX, you may need to select a new virtual CPU model that exposes the instruction set to the underlying VM. Please see this post on the Proxmox forum.
For a clean install where you do not have proper CPU support, you can run MongoDB externally. See the external MongoDB fresh install section for instructions on how to do so.
When upgrading the controller to v6, you need to upgrade MongoDB from v3 to v8. Details about the migration process can be found in mongodb_upgrade but if you're here, it's probably because one of these two checks for AVX (amd64) or armv8.2-a (arm64) have failed when you attempted to upgrade, meaning that your CPU is not supported. The quick way to get your controller back up and running is to move back to the v5 image as a failed upgrade will not have modified any of your data and you can start your v5 controller container back up and be running just fine.
Longer term, you may want to upgrade to v6 but you still can't just upgrade because you're stuck with a CPU that doesn't have the required capabilities to support a modern MongoDB. This can be achieved by running MongoDB external to the Omada Controller application. If you're ready to try to migrate from the all in one deployment to separate MongoDB and Omada Controller containers, check out the Migration from All in One instructions. As always, make sure that you take a backup before you do anything like this as I am sure you do before all upgrades, right?
106 followers · starred Dec 2025
1,522 followers · starred Nov 2022
323 followers · starred Apr 2025
477 followers · starred Apr 2025
Docker image for TP-Link Omada Software Controller (also known as TP-Link Omada Network Application) to centrally manage TP-Link Omada Hardware.
[!NOTE] Disclaimer: I am not, in any way, affiliated with TP-Link. I am just a community member who has packaged TP-Link's free software as a Docker image for easier management and consumption.
For instructions on running a legacy v3 or v4 controller, see the README for v3 and v4. See the tag archive_v3_v4 for a snapshot of the code that includes the v3 and v4 artifacts as they have been removed as of July 2, 2024.
If you have issues running the controller, feel free to create a Help discussion and I will help as I can. If you are specifically having a problem that is related to the actual software, I would suggest filing an issue on the TP-Link community forums or contacting TP-Link's support team as I do not have access to source code to debug those issues. If you're not sure where the problem might be, I can help determine if it is a running in Docker issue or a software issue. If you're certain you have found a bug, create a Bug Report Issue.
If you don't know much about Docker or want to just get started as easily as possible, start here as this will guide you through this readme on key concepts and things you want to take into consideration.
major version tagged - For people who want to set it and forget it (i.e. - if you are used to just using latest), there is also the major tag version (i.e. - 5, 6, etc) which should be mostly safe from non-breaking changes.major.minor version tagged - Most people will want to use a major.minor tag version (i.e. - 6.3) as this is the safest option and can almost certainly be considered to be non-breaking when a new version of the image is available.latest TAG IS A BAD IDEA - DO NOT DO IT! Using latest with software like this is not a good idea because you may upgrade to a version that your devices do not support and TP-Link can put in breaking changes at any time! Instead, use one of the two tag types above.amd64 so only use them if you really need that functionality.6.3) tag6.3) taghttps://<ip-address-or-hostname>:8043/, assuming you're using the default ports.There are three main options for upgrading from v5 to v6:
This upgrade guide will focus on using the MongoDB upgrade container. The native controller migration and backup & restore procedures are using built in controller capabilities so you should follow TP-Link documentation but those details are out of scope of this guide.
[!IMPORTANT] The upgrade requires a manual step of a MongoDB upgrade which is automated but it has to be run as a separate container while the controller is stopped.
There are a few reasons for the manual upgrade:
Now that you understand why there is manual step required, see the MongoDB upgrade guide for detailed instructions on how to proceed with the upgrade.
If you tried to run the v6 controller without doing the upgrade, see HELP! My Controller Stopped Working! for steps to get back to a working state or what you need to do to get back to a state where you can perform the upgrade.
For every version at any given point in time, there are many different tags: major, major.minor, and a full major.minor.patch.build tag. For guidance on what image tag you should use, check out the Picking an image tag section of the quickstart guide above but the gist of it is that most people will want to use either a major or major.minor version tag.
[!IMPORTANT] The
latesttag still points to the latest v5 version of the software even thought v6 is available. This will not change in the future as I am deprecating the use of thelatesttag due to the inability for me to perform a migration for v5 to v6 without manual user intervention and not wanting to break anyone's controller who is using an auto-update mechanism. Review the quickstart guide on tag versions to understand the tagging methodology or review the version tables below.
For a full tag list, search the Docker Hub tags list. The following tags have multi-arch support for amd64 and arm64 and will automatically pull the correct tag based on your system's architecture.
These are builds with the standard OpenJDK JVM.
| Tag(s) | Major.Minor Release | Current Version |
|---|---|---|
6, 6.3 | 6.3.x | 6.3.0.45 |
6.2 | 6.2.x | 6.2.14.11 |
These are builds with OpenJ9; an alternative to the standard OpenJDK JVM implementation. OpenJ9 is optimized for low memory footprint and fast startup time.
[!NOTE] Work on optimizing the OpenJ9 builds is still ongoing but you will likely see a reduced memory footprint so these builds are ideal for situations where memory usage is more of a concern.
| Tag(s) | Major.Minor Release | Current Version |
|---|---|---|
6-openj9, 6.3-openj9, 6.3.0.45-openj9 | 6.3.x | 6.3.0.45 |
6.2-openj9, 6.2.14.11-openj9 | 6.2.x | 6.2.14.11 |
If for some reason you can't use the multi-arch tags, there are explicitly tagged images with the architecture (-amd64 and -arm64) appended to them. Check Docker Hub for the full list of tags.
If you need a specific version of the controller, starting with 5.13 and 5.14, there are explicitly tagged images with the exact version (i.e. - 6.3.0.45) in the tag name. Check Docker Hub for the full list of tags.
These are multi-arch tags. For the full tag listings, see the Docker Hub tags above but the general format for beta-* and *-openj9 follow for the specific architecture tags. OpenJ9 images are only available for amd64 and arm64.
| Tag(s) | Major.Minor Release | Current Version |
|---|---|---|
beta, beta-6.3, beta-6.3.0.111 | 6.3.x Beta | 6.3.0.111 |
beta-openj9, beta-6.3-openj9, beta-6.3.0.111-openj9 | 6.3.x Beta w/OpenJ9 | 6.3.0.111 |
[!WARNING] Do NOT run the
armv7l(32 bit) images. Upgrade your operating system toarm64(64 bit) unless you accept that you're running an outdated MongoDB, a base operating system with unpatched vulnerabilities, an old version of Java, and a controller that will never be upgraded beyond5.15.8.2! See the Known Issues section for more information.
These images are still published on Docker Hub but are no longer regularly updated due to the controller software no longer being updated. Use with extreme caution as these images are likely to contain unpatched security vulnerabilities!. See Archived Tags for v3 and v4 for details on the old, unmaintained image tags.
| Tag(s) | Major.Minor Release | Current Version |
|---|---|---|
6.1 | 6.1.x | 6.1.0.19 |
6.1-openj9, 6.1.0.19-openj9 | 6.1.x | 6.1.0.19 |
6.0 | 6.0.x | 6.0.0.25 |
6.0-openj9, 6.0.0.25-openj9 | 6.0.x | 6.0.0.25 |
latest, 5, 5.15 | 5.15.x | 5.15.24.19 |
5.15-openj9, 5.15.24.19-openj9 | 5.15.x | 5.15.24.19 |
5.14 | 5.14.x | 5.14.32.4 |
5.14-openj9, 5.14.32.4-openj9 | 5.14.x w/OpenJ9 | 5.14.32.4 |
5.13 | 5.13.x | 5.13.30.8 |
5.13-chromium | 5.13.x | 5.13.30.8 |
5.13-openj9, 5.13.30.8-openj9 | 5.13.x w/OpenJ9 | 5.13.30.8 |
5.12 | 5.12.x | 5.12.7 |
5.12-chromium | 5.12.x | 5.12.7 |
5.9 | 5.9.x | 5.9.31 |
5.9-chromium | 5.9.x | 5.9.31 |
5.8 | 5.8.x | 5.8.4 |
5.8-chromium | 5.8.x | 5.8.4 |
5.7 | 5.7.x | 5.7.4 |
5.7-chromium | 5.7.x | 5.7.4 |
5.6 | 5.6.x | 5.6.3 |
5.6-chromium | 5.6.x | 5.6.3 |
5.5 | 5.5.x | 5.5.6 |
5.5-chromium | 5.5.x | 5.5.6 |
5.4 | 5.4.x | 5.4.6 |
5.4-chromium | 5.4.x | 5.4.6 |
5.3 | 5.3.x | 5.3.1 |
5.3-chromium | 5.3.x | 5.3.1 |
5.1 | 5.1.x | 5.1.7 |
5.1-chromium | 5.1.x | 5.1.7 |
5.0 | 5.0.x | 5.0.30 |
Going forward, Chromium is no longer required as of 5.14. If you were using a Chromium tag, go back to a normal tag. All reports should now either by CSV or XLSX format.
In order to ensure that the container does not attempt to use excessive amounts of CPU or memory, it is strongly suggested that you set resource constraints on your container. The examples do not specifically have those set as the values you set may depend on your system's available resources. A safe starting point, which I personally use, is the docker run... equivalent of --cpus 2 --memory 4g --memory-swap 5g. Adapt this for whatever method you use whether it's docker run..., docker compose, k8s manifests, or the Helm chart.
If you wish to modify the default JVM arguments of -Xms128m -Xmx1024m to something else, you can do so by utilizing the _JAVA_OPTIONS environment variable. See the Low Resource Systems section of the known issues for more details.
While you can take backups of your controller by making a copy of the persistent data, the chance of data corruption exists if you do so while the container is running as there is a database used for persistence. The best way to take backups is to use the automatic backup capabilities within the controller itself. Go to Settings > Maintenance > Backup and scroll down to Auto Backup to enable and configure the feature. These backups can be restored as a part of the installation process on a clean controller install. If you do not see Settings > Maintenance, you may be drilled down into a sites' configuration. Make sure you're in the Global view as settings that impact the controller as a whole, like backups, are in that Global view.
Backups can also be taken manually on the same screen as the auto backup settings. This would be ideal to do before you perform an upgrade to ensure that you are able to roll back in case of issues upon upgrade as you can not move from a newer version of the controller to an older version! It will break the database and require you to do a full reinstall!
If you do want to just take a snapshot of your persistent data, make sure you stop the container cleanly, tar/zip/snapshot the data in some way, and then start the container back up to bring the controller back online.
Before performing any upgrade, I would suggest taking a backup through the controller itself. Controller upgrades are done by stopping the existing container gracefully (see the note below on this topic), removing the existing container, and running a new container with the new version of the controller. This can be done manually, with compose, or with many other 3rd party tools which auto-update containers, such as Watchtower.
When stopping your container in order to upgrade the controller, make sure to allow the MongoDB enough time to safely shutdown. This is done using docker stop -t <value> where <value> is a number in seconds, such as 60, which should allow the controller to cleanly shutdown. Database corruption has been observed when not cleanly shut down. The docker run and compose examples now include --stop-timeout and stop_grace_period which are set to 60s.
There are pre-built images on Docker Hub - you only need to build your own image if you either don't trust pre-built images or want to do something custom to the image.
There are some differences between the build steps for amd64, arm64, and armv7l. These changes will happen automatically if you use the build-args INSTALL_VER and ARCH. For possible INSTALL_VER values, see mbentley/docker-omada-controller-url:
amd64No build args required; set for the default build-args
docker build \
--build-arg BASE=mbentley/ubuntu:24.04 \
--build-arg INSTALL_VER="6.3.0.45" \
--build-arg ARCH="amd64" \
-f Dockerfile \
-t mbentley/omada-controller:6.3-amd64 .
arm64Only the ARCH build-arg is required
docker build \
--build-arg BASE=mbentley/ubuntu:24.04 \
--build-arg INSTALL_VER="6.3.0.45" \
--build-arg ARCH="arm64" \
-f Dockerfile \
-t mbentley/omada-controller:6.3-arm64 .
armv7l[!WARNING] The
armv7lversion was deprecated and support has been removed for versions beyond5.15.8.2.
Both the ARCH and BASE build-args are required
docker build \
--build-arg INSTALL_VER="5.15.8.2" \
--build-arg ARCH="armv7l" \
--build-arg BASE="ubuntu:16.04" \
-f Dockerfile \
-t mbentley/omada-controller:5.15-armv7l .
[!NOTE] Regardless of how you run the container, I would strongly recommend adding CPU and memory limits for the container. See the section on Container Resource Constraints in the Best Practices for Operation section of the documentation.
These example below are based on docker run... commands. See Using Docker Compose for compose examples or Using k8s for example k8s manifests. See Optional Environment Variables for details on the environment variables that can modify the behavior of the controller inside the container. To run this Docker image and keep persistent data in named volumes:
net=hostUsing host networking mode is the preferred method of running the controller. In order to use the host's network namespace, you must first ensure that there are not any port conflicts. The docker run command is the same except for that all of the published ports should be removed and --net host should be added. Technically it will still work if you have the ports included, but Docker will just silently drop them. Here is a snippet of what the above should be modified to look like:
docker run -d \
--name omada-controller \
--stop-timeout 60 \
--restart unless-stopped \
--ulimit nofile=4096:8192 \
--net host \
-e TZ=Etc/UTC \
-v omada-data:/opt/tplink/EAPController/data \
-v omada-logs:/opt/tplink/EAPController/logs \
mbentley/omada-controller:6.3
When is comes to device management, using port mapping is more complex than using host networking as your devices need to be informed of the controller's IP or hostname. For instructions on how to configure your device for adoption, see the device adoption readme. If you do not follow these instructions, it is highly likely that new devices will fail to adopt!
[!WARNING] If you want to change the controller ports from the default mappings, you absolutely must update the port binding inside the container via the environment variables. The ports exposed must match what is inside the container. The Omada Controller software expects that the ports are the same inside the container and outside and will load a blank page if that is not done. See #99 for details and and example of the behavior.
docker run -d \
--name omada-controller \
--stop-timeout 60 \
--restart unless-stopped \
--ulimit nofile=4096:8192 \
-p 8088:8088 \
-p 8043:8043 \
-p 8044:8044 \
-p 8843:8843 \
-p 19810:19810/udp \
-p 27001:27001/udp \
-p 29810:29810/udp \
-p 29811-29817:29811-29817 \
-e TZ=Etc/UTC \
-v omada-data:/opt/tplink/EAPController/data \
-v omada-logs:/opt/tplink/EAPController/logs \
mbentley/omada-controller:6.3
[!WARNING] In most cases, overriding the default port numbers using environment variables mostly ONLY works the first time the container is started by default. After the database is initialized, the controller app reads the port configuration from the database and any modification of the ports through the controller's web interface will also be persisted to the database as well. If you wish to modify the ports using the environment variables on subsequent container startups, you must also set the
WEB_CONFIG_OVERRIDEenvironment variable totrueand they'll be re-read on next startup.
There are two sets of ports for the main controller (not including the specific ports used for a variety of management functions; see the Optional Environment Variables section for full details on those):
MANAGE_*_PORT environment variables, can only be modified using the environment variables on the first time the controller is started. If persistent data exists, changing the controller's ports via environment variables will have no effect on the controller itself and can only be modified through the web UI.PORTAL_*_PORT environment variables.[!TIP] If you make changes to the ports via the web UI, you should update the environment variables to match as well, just to be safe as there are some odd quirks to how the ports are read.
There is an optional ability to run the container in a rootless mode. This version has fewer pre-flight capabilities to do tasks like set permissions for you but works in environments where running containers as root is blocked (i.e. - many Kubernetes environments). To activate the rootless entrypoint the following conditions must be met:
ROOTLESS to truePUID and PGID variables do not apply heredata and logs/certs path when injecting your own certificates, must be readable by the user in which you're running asThere are a few Docker Compose files available that can serve as a guide if you want to use compose to managed the lifecycle of your container. Depending on which network mode of operation you want to use, there are example for each: host networking or bridge/port mapping.
<download the compose file you wish to use>
<edit the compose file to match your persistent data needs>
docker compose up -d
There are two available options for deployment to Kubernetes:
The Helm charts are published to mbentley/omada-controller-helm on Docker Hub but are also available directly from this repo. For a Helm release list and detailed usage instructions, check out the Helm chart's README.
Examples of how to deploy a complete stack of MongoDB and Omada Controller using Helm can be found in the k8s/helm directory. Follow the README to get started. Review the values files to see how to configure the chart for your environment.
The example manifests are in the k8s/manifests directory. It's assumed that you will know how to modify and use these manifests on k8s if you choose that as your deployment option.
| Variable | Default | Values | Description | Valid For |
|---|---|---|---|---|
_JAVA_OPTIONS | null | any valid JVM args | Overrides the default JVM memory arguments (e.g. -Xms128m -Xmx1024m); takes priority over CLI args without modifying them; see Low Resource Systems for more details | >= 3.2 |
APPLICATION_PROPERTIES | null | any Spring Boot properties | Content written verbatim to application.properties on the classpath at startup, loaded by Spring Boot; see Spring Boot Tuning for details and recommended settings | >= 5.x |
EAP_MONGOD_URI | null | mongodb://user:pass@1.2.3.4:27017/omada | Used to specify the URI of MongoDB when running it external to the controller container | >= 5.x |
JAVA_MAX_HEAP_SIZE | null | any valid JVM heap size | Replaces the hardcoded -Xmx value in the default CMD (e.g. 512m, 1g); works with any JVM including HotSpot | >= 3.2 |
JAVA_MIN_HEAP_SIZE | null | any valid JVM heap size | Replaces the hardcoded -Xms value in the default CMD (e.g. 64m, 128m); works with any JVM including HotSpot | >= 3.2 |
MANAGE_HTTP_PORT | 8088 | 1024-65535 | Management portal HTTP port; for ports < 1024, see Unprivileged Ports | >= 3.2 |
MANAGE_HTTPS_PORT | 8043 | 1024-65535 | Management portal HTTPS port; for ports < 1024, see Unprivileged Ports | >= 3.2 |
MONGO_EXTERNAL | false | true, false | Disables MongoDB from starting inside the controller container; used for external MongoDB | >= 5.x |
MONGOD_EXTRA_ARGS | null | any valid mongod args | Appends extra arguments to every embedded mongod invocation (e.g. --wiredTigerCacheSizeGB 0.25); not supported in rootless mode | >= 5.x |
PGID | 508 | any | Set the omada process group ID ` | >= 3.2 |
PGROUP | omada | any | Set the group name for the process group ID to run as | >= 5.0 |
PORTAL_HTTP_PORT | 8088 | 1024-65535 | User portal HTTP port; for ports < 1024, see Unprivileged Ports | >= 4.1 |
PORTAL_HTTPS_PORT | 8843 | 1024-65535 | User portal HTTPS port; for ports < 1024, see Unprivileged Ports | >= 4.1 |
PORT_APP_DISCOVERY | 27001 | 1024-65535 | Omada Controller can be discovered by the Omada APP within the same network through this port | >= 5.x |
PORT_DISCOVERY | 29810 | 1024-65535 | Omada Controller and Omada Discovery Utility discover Omada devices | >= 5.x |
PORT_MANAGER_V1 | 29811 | 1024-65535 | Omada Controller and Omada Discovery Utility manage the Omada devices running firmware fully adapted to Omada Controller v4* | >= 5.x |
PORT_ADOPT_V1 | 29812 | 1024-65535 | Omada Controller and Omada Discovery Utility manage the Omada devices running firmware fully adapted to Omada Controller v4* | >= 5.x |
PORT_UPGRADE_V1 | 29813 | 1024-65535 | When upgrading the firmware for the Omada devices running firmware fully adapted to Omada Controller v4*. | >= 5.x |
PORT_MANAGER_V2 | 29814 | 1024-65535 | Omada Controller and Omada Discovery Utility manage the Omada devices running firmware fully adapted to Omada Controller v5* | >= 5.x |
PORT_TRANSFER_V2 | 29815 | 1024-65535 | Omada Controller receives Device Info and Packet Capture files from the Omada devices | >= 5.9 |
PORT_RTTY | 29816 | 1024-65535 | Omada Controller establishes the remote control terminal session with the Omada devices | >= 5.9 |
PORT_DEVICE_MONITOR | 29817 | 1024-65535 | Omada Controller ??? (currently unknown the full purpose) | >= 06.0 |
PUID | 508 | any | Set the omada process user ID ` | >= 3.2 |
PUSERNAME | omada | any | Set the username for the process user ID to run as | >= 5.0 |
ROOTLESS | false | true, false | Sets the entrypoint for rootless mode | >= 5.14 |
SHOW_SERVER_LOGS | true | true, false | Outputs Omada Controller logs to STDOUT at runtime | >= 4.1 |
SHOW_MONGODB_LOGS | false | true, false | Outputs MongoDB logs to STDOUT at runtime | >= 4.1 |
SKIP_USERLAND_KERNEL_CHECK | false | true, false | When set to true, skips the userland/kernel match check for armv7l & arm64 | >= 3.2 |
SSL_CERT_NAME | tls.crt | any | Name of the public cert chain mounted to /cert; see Custom SSL Certificates | >= 3.2 |
SSL_KEY_NAME | tls.key | any | Name of the private cert mounted to /cert; see Custom SSL Certificates | >= 3.2 |
TLS_1_11_ENABLED | false | true, false | Re-enables TLS 1.0 & 1.1 if set to true | >= 4.1 |
TZ | Etc/UTC | <many> | See Time Zones for more detail | >= 3.2 |
UPGRADE_ES_HTTPS_PORT | 8044 | 1024-65535 | Dedicated HTTPS port for upgrades for ES series devices | >= 6.3.0.36 |
UPGRADE_HTTPS_PORT | 8043 | 1024-65535 | Dedicated HTTPS port for upgrades, separate from the main Controller port | >= 6.1 |
WEB_CONFIG_OVERRIDE | false | true, false | Forces the controller to re-read port configuration from omada.properties on startup; see Using non-default ports | >= 5.x |
Documentation on the ports used by the controller can be found in the TP-Link FAQ.
In the examples, there are two directories where persistent data is stored: data and logs. The data directory is where the persistent database data is stored where all of your settings, app configuration, etc is stored. The log directory is where logs are written and stored. I would suggest that you use a bind mounted volume for the data directory to ensure that your persistent data is directly under your control and of course take regular backups within the Omada Controller application itself. Previous versions of the controller (before 5.x) also used a work persistent directory omada-work which was mapped to /opt/tplink/EAPController/work inside the container where the application was deployed. This work directory is no longer needed as of 5.0.x.
By default, Omada software uses self-signed certificates. If however you want to use custom certificates you can mount them into the container as /cert/tls.key and /cert/tls.crt. The tls.crt file needs to include the full chain of certificates, i.e. cert, intermediate cert(s) and CA cert. This is compatible with kubernetes TLS secrets. Entrypoint script will convert them into Java Keystore used by jetty inside the Omada SW. If you need to use different file names, you can customize them by passing values for SSL_CERT_NAME and SSL_KEY_NAME as seen above in the Optional Environment Variables section.
[!WARNING] As of the version 4.1, certificates can also be installed through the web UI. You should not attempt to mix certificate management methods as installing certificates via the UI will store the certificates in MongoDB and then the
/certvolume method will cease to function. If you installed certificates using the UI and want to revert this - see this discussion.
By default, this image uses the Etc/UTC time zone. You may update the time zone used by passing a different value in the TZ variable. See List of tz database time zones for a complete list of values in the TZ identifier table column.
This Docker image runs as a non-root user by default. In order to bind unprivileged ports (ports < 1024 by default), you must include --sysctl net.ipv4.ip_unprivileged_port_start=0 in your docker run command to allow ports below 1024 to be bound by non-root users.
If you are interested in using the Omada Controller APIs to retrieve data from the controller, the latest version of the API documentation that I have found is available from the community forums in this post. I'm not able to provide support for the APIs but I've found them to be helpful for my own usage and they weren't easy to find.
Users who are using bridge mode often report that routers, switches, and EAPs fail to adopt. This is due to the controller being technically being on a different network inside the container's bridge network, exporting the ports via NAT. Using port mapping is more complex than using host networking as your devices need to be informed of the controller's IP or hostname. See the device adoption docs for instructions on how to configure this on your device(s) prior to attempting to adopt them.
While MongoDB is fairly robust, the persistent data can become corrupt if a clean shutdown isn't performed. By default, Docker only waits 10 seconds before killing the container processes when using docker stop.... I would highly recommend performing a stop with a large timeout value, such as docker stop -t 30... to ensure that the controller is cleanly shut down. This value may need to be even larger for low powered devices, such as a Raspberry Pi.
armv7l[!WARNING] Deprecation and Removal Notice - armv7l images will no longer be available starting with the v5.15.20 and later versions. See this issue describing the change. The last version that will be available for
armv7lis5.15.8.2.Do not run the Omada Controller on your
armv7l/armhf(32 bit arm) based operating system! If you're running as Raspberry Pi 3, 4, Pi Zero 2W, you should run a 64 bit operating system so you can use thearm64image which is supported. At any time, TP-Link can break compatibility with 32 bit arm and there will be no upgrade path forward! You have been warned!
armv7lAll armv7l images are based on Ubuntu 16.04 due to the lack of packaging for MongoDB in newer Ubuntu releases. Ubuntu 16.04 is end of general support so security patches aren't regularly being released. I would highly recommend not using the armv7l images unless you have no other alternative and accept the security risk. If you are running a Raspberry Pi, I might suggest looking into running an arm64 based operating system if your system supports it (the Raspberry Pi 3 and above do)
The armv7l architecture is 32 bit and MongoDB is no longer available as a pre-compiled binary in Ubuntu, this means that the armv7l images are running version 2.6.10 of MongoDB. This may lead to unexpected behavior as TP-Link states Omada Controller version 4.1.x and newer require at least MongoDB 3.0.15 or newer, depending on which version of the controller you're running. For the armv7l architecture, I will continue to include those in the builds until they stop working as I can't guarantee that an update will not actually require a newer MongoDB feature that isn't available.
Systems such as Raspberry Pis or Kubernetes pods with tight memory limits may not have sufficient memory to run with the default memory settings of this image. The default JVM heap is -Xmx1024m and MongoDB's WiredTiger cache defaults to half of available RAM, which together can push memory usage well above 1.5 GB.
The default CMD includes hardcoded -Xmx1024m -Xms128m. These can already be overridden by JVM-specific environment variables such as _JAVA_OPTIONS, but those do not change the visible command line. If you want the effective heap settings to also be reflected in ps output, you can use JAVA_MAX_HEAP_SIZE and JAVA_MIN_HEAP_SIZE, which replace the existing -Xmx and -Xms arguments in the startup command.
The available approaches are:
Setting JAVA_MAX_HEAP_SIZE (and optionally JAVA_MIN_HEAP_SIZE). This directly replaces the -Xmx (and -Xms) value in the startup command and is visible in ps output. Works with any JVM, including HotSpot and OpenJ9.
JAVA_MAX_HEAP_SIZE=512m
JAVA_MIN_HEAP_SIZE=128m
Setting the _JAVA_OPTIONS environment variable; for example, _JAVA_OPTIONS="-Xms128m -Xmx768m". This already takes priority over the CLI args on HotSpot, but it does not modify the visible command line so tools like ps will still show the original values.
Overriding the CMD as seen in this issue here.
By default MongoDB will use up to half of available RAM for its WiredTiger cache. On a system with 2 GB RAM this means up to 1 GB for MongoDB alone. You can limit it with MONGOD_EXTRA_ARGS:
MONGOD_EXTRA_ARGS=--wiredTigerCacheSizeGB 0.25
[!NOTE]
MONGOD_EXTRA_ARGSis not supported in rootless mode.
If memory footprint is a priority, consider using one of the OpenJ9 image tags (e.g. 6.3-openj9). Eclipse OpenJ9 is optimized for low memory use and fast startup time. In practice it can reduce the Java process RSS by 30-50% compared to HotSpot at the same -Xmx setting due to more aggressive idle GC and shared class caching.
When using an OpenJ9 image you can additionally tune idle behaviour:
OPENJ9_JAVA_OPTIONS=-XX:+IdleTuningGcOnIdle -XX:+IdleTuningCompileAtIdle
[!NOTE]
OPENJ9_JAVA_OPTIONSis appended after the JVM command line in OpenJ9, so it takes precedence over the hardcoded-Xmx1024m. If you also setJAVA_MAX_HEAP_SIZE/JAVA_MIN_HEAP_SIZE, the values should match so the effective heap settings and the visible command line stay consistent.
The following environment variables result in approximately 1–1.3 GB RSS in practice, leaving headroom in a 2 GB pod limit:
# Image tag: mbentley/omada-controller:6.3-openj9
# (or a pinned version such as 6.3.0.45-openj9)
JAVA_MAX_HEAP_SIZE: "512m"
JAVA_MIN_HEAP_SIZE: "128m"
MONGOD_EXTRA_ARGS: "--wiredTigerCacheSizeGB 0.25"
OPENJ9_JAVA_OPTIONS: "-Xmx512m -Xms128m -XX:+IdleTuningGcOnIdle -XX:+IdleTuningCompileAtIdle"
In this example the heap values are intentionally duplicated. On OpenJ9, OPENJ9_JAVA_OPTIONS is authoritative; JAVA_MAX_HEAP_SIZE and JAVA_MIN_HEAP_SIZE are included so the startup command line shown by tools such as ps matches the effective settings. On HotSpot, OPENJ9_JAVA_OPTIONS is ignored and the JAVA_*_HEAP_SIZE values remain effective.
Approximate memory budget:
| Component | Approx. |
|---|---|
Java heap (-Xmx) | 512 MB |
| Java metaspace + JIT code cache | ~150 MB |
| MongoDB WiredTiger cache | 256 MB |
| OS / JVM / MongoDB overhead | ~300 MB |
| Total | ~1.2–1.4 GB |
In one Kubernetes deployment with the embedded MongoDB, the following settings worked reliably on a small network with 1 switch, 3 APs, and about 40 clients:
JAVA_MAX_HEAP_SIZE: "512m"
JAVA_MIN_HEAP_SIZE: "128m"
MALLOC_ARENA_MAX: "2"
MONGOD_EXTRA_ARGS: "--wiredTigerCacheSizeGB 0.25"
OPENJ9_JAVA_OPTIONS: "-Xquickstart -Xcodecachetotal16m -XcompilationThreads1 -Xss192k -Xmso192k -XX:-TransparentHugePage -XX:+IdleTuningGcOnIdle"
This tuning reduced OpenJ9's non-heap overhead significantly (smaller JIT code cache, fewer JIT compiler threads, smaller thread stacks, and disabled transparent huge pages), but the total pod memory still fluctuated around roughly 0.9-1.1 GiB because the container includes both the Java controller and mongod.
Treat this as a practical reference point rather than a universal recommendation. Larger sites, more devices, more clients, firmware upgrades, or long-running background tasks may require more headroom.
Changing these values would be necessary on these low resource systems to prevent the operating system from killing the container due to it thinking it can allocate more memory than it should. The controller process may still actually functionally require more memory so your mileage may vary in terms of the impact of running on such a low resource system.
For advanced tuning of Spring Boot internals (Tomcat thread pool, async executors, logging levels), see Spring Boot Tuning.
If a Raspberry Pi 4 is running a 32 bit version of Raspberry Pi OS, a recent firmware update has intentionally made it so the default kernel the Pi will boot from has been switched from 32 bit kernel to a 64 bit kernel. This is a problem for the running container because the version of MongoDB that is present in the armv7l image (also known as armhf), will fail to start on a 64 bit kernel. Most software tends to run fine when switching kernels but in this case, it will prevent the controller from running due to MongoDB failing to start. Please also review the Notes for armv7l to also understand the risks for running the armv7l based controller!
To fix this issue in the short term, you will want to instruct your Pi to boot from a 32 bit kernel instead of the 64 bit kernel by:
arm_64bit=0 to the /boot/config.txt fileA proper long term solution would be to reinstall Raspberry Pi OS on your Pi 4 and use the new arm64 based operating system which will get you a 64 bit userland and a 64 bit kernel. There are significant known issues while running the armv7l image on a device so longer term, this is the best solution.
It has been reported that a number of users are seeing 404s or blank pages after upgrading to version 5.8 and 6.3. This can be resolved by either force-reloading the page or by clearing your browser's cache.
[!NOTE] Additionally for 6.3 - the login URL has changed from
https://<your-ip-or-hostname>:8043/logintohttps://<your-ip-or-hostname>:8043/independent/index.html#login. Update any bookmarks you may have to the login page.
It has been reported that users of some NAS devices such as a Synology or users of a Docker management UI like Portainer have had issues with upgrades due to the CMD being retained between versions. This normally does not happen with the Docker command line so it is a bit of an unexpected pattern but it can not be overwritten as it exists outside of the container.
If updating from 3.x to 4.x or 4.x to 5.x, make sure to completely re-create the container (leaving your persistent data intact) otherwise the controller will not start. This is due to the CMD changing between the major releases as some web interfaces like Synology or Portainer retain the entrypoint and command explicitly instead of inheriting it from the image. To resolve the issue, do one of the following:
/usr/bin/java -server -Xms128m -Xmx1024m -XX:MaxHeapFreeRatio=60 -XX:MinHeapFreeRatio=30 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tplink/EAPController/logs/java_heapdump.hprof -Djava.awt.headless=true -cp /opt/tplink/EAPController/lib/*::/opt/tplink/EAPController/properties: com.tplink.smb.omada.starter.OmadaLinuxMainIt should be noted that users of 3.x who wish to upgrade to 4.x must perform specific upgrade steps to prevent data loss!
There is a known bug in the controller software where a user is not able to login with their local user after upgrading to 5.12.x. This has been reported to TP-Link but a fix has not yet been provided.
In versions 5.8 to 5.12, it has been seen where Safari will take a significant amount of time to completely load a page in the controller web interface. This is an issue that has been reported upstream.
Upon upgrade to 5.14, the controller may not start. You may see error messages that include phrases like: Cannot retry start up springboot, Unsatisfied dependency expressed through field..., org.springframework.beans.factory.UnsatisfiedDependencyException, among others. This is a problem with the controller software itself that TP-Link is aware of. If you're impacted, see the first post in this issue for possible workaround instructions and more information. This issue should no longer be present on the latest 5.14 versions.
[!CAUTION] Do NOT use this override environment variable unless you need it. It may cause unexpected issues in the future. Remove the environment variable if you're no longer running on 5.15.6.x.
Upon upgrade to 5.15.6.x, the controller may not start. You may see error messages right around the Valid radius server keystore is missing. Generating one ... message that include phrases like: Exception in thread "main" java.lang.NoSuchFieldError: id_alg_zlibCompress among others. This is a problem with the controller software itself that TP-Link is aware of. If you're impacted, see the first post in this issue for more information. An environment variable can be set as WORKAROUND_509=true on the container definition and it will delete two library files that are causing the issue.
In order to run v6 Omada Controller using the images I publish, you will need to be able to run MongoDB 8 but unfortunately MongoDB does not run on older CPUs if they lack specific instructions or features. There are options which will allow you to run v6 of the controller but longer term, I would suggest upgrading your system to support these instruction sets or features required but I understand that means financial investment.
[!TIP] Proxmox users: if your host's CPU supports AVX, you may need to select a new virtual CPU model that exposes the instruction set to the underlying VM. Please see this post on the Proxmox forum.
For a clean install where you do not have proper CPU support, you can run MongoDB externally. See the external MongoDB fresh install section for instructions on how to do so.
When upgrading the controller to v6, you need to upgrade MongoDB from v3 to v8. Details about the migration process can be found in mongodb_upgrade but if you're here, it's probably because one of these two checks for AVX (amd64) or armv8.2-a (arm64) have failed when you attempted to upgrade, meaning that your CPU is not supported. The quick way to get your controller back up and running is to move back to the v5 image as a failed upgrade will not have modified any of your data and you can start your v5 controller container back up and be running just fine.
Longer term, you may want to upgrade to v6 but you still can't just upgrade because you're stuck with a CPU that doesn't have the required capabilities to support a modern MongoDB. This can be achieved by running MongoDB external to the Omada Controller application. If you're ready to try to migrate from the all in one deployment to separate MongoDB and Omada Controller containers, check out the Migration from All in One instructions. As always, make sure that you take a backup before you do anything like this as I am sure you do before all upgrades, right?
106 followers · starred Dec 2025
1,522 followers · starred Nov 2022
323 followers · starred Apr 2025
477 followers · starred Apr 2025