Deploy rootless using Docker Compose
This guide shows you how to deploy F5 NGINX Instance Manager using an official rootless Docker image and Docker Compose. F5 builds and maintains this image, so you don’t need to build it yourself. All container processes run as a non-root user (nms).
The image includes NGINX Instance Manager, Security Monitoring, and the latest F5 WAF for NGINX compiler.
This deployment has the following key characteristics:
- Rootless by design. All processes run as
nms. The container needs no elevated privileges at runtime. - Consolidated storage.
nim-dataholds the database, secrets, and certificates in one/datavolume. Separate volumes hold WAF compiler artifacts, outbound CA trust, and ClickHouse data. - First-boot initialization. On first start, the container seeds certificates and credentials automatically.
- Maintenance mode. Start the container without NGINX Instance Manager services. This lets you back up, restore, or debug safely.
- Built-in watchdog. The watchdog monitors critical NGINX Instance Manager processes. If one fails, the watchdog stops the container cleanly.
Before you begin, make sure you have the following:
- Docker Engine 20.10 or later on a Linux host.
- Docker Compose plugin v2 or later.
- A JWT from your MyF5 subscriptions page, to authenticate with the F5 image registry.
- Download the docker-compose-rootless.yaml file
- ClickHouse. This compose file starts it as a bundled service.
- At least 4 CPU cores and 4 GB of memory.
Create the admin password file in the same directory as docker-compose-rootless.yaml:
echo "<password>" > admin_password.txtLog in to the F5 image registry with your JWT:
docker login private-registry.nginx.com --username=<JWT_CONTENTS> --password=noneStart the stack:
docker compose -f docker-compose-rootless.yaml up -dNGINX Instance Manager is available at https://localhost:8443. Log in with the admin credentials you configured.
| Variable | Required | Description |
|---|---|---|
NIM_CLICKHOUSE_ADDRESSPORT |
Yes | ClickHouse address and port, for example clickhouse:9000. |
NIM_USERNAME |
Yes | Admin username, set on first boot. |
NIM_PASSWORD |
Yes | Admin password, set on first boot. |
NIM_CLICKHOUSE_USERNAME |
No | ClickHouse username. |
NIM_CLICKHOUSE_PASSWORD |
No | ClickHouse password. |
NIM_LICENSE |
No | Base64-encoded license. Activates on first boot if set. Otherwise, activate manually in the UI after deployment. |
NIM_LOG_LEVEL |
No | Logging verbosity: INFO, DEBUG, and so on. |
NIM_METRICS_TTL |
No | Metrics retention, in days (integer). |
NIM_EVENTS_TTL |
No | Events retention, in days (integer). |
NIM_SECURITY_TTL |
No | Security events retention, in days (integer). |
NIM_WATCHDOG_TIMEOUT |
No | Watchdog timeout, in seconds (integer). |
NIM_LICENSE_MODE_OF_OPERATION |
No | connected (default) or disconnected. |
NIM_MAINTENANCE |
No | Set to true to start in maintenance mode. No services launch. Required before running a restore. |
ENABLE_METRICS |
No | true or false. |
PROXY_ENABLE |
No | true or false. Turns on a forward proxy. |
PROXY_HOST |
No | Hostname or IP address of the proxy server. |
PROXY_PORT |
No | Proxy port. Default is 3128. |
PROXY_PROTOCOL |
No | http (default) or https. |
PROXY_AUTH_REQUIRED |
No | true or false. |
PROXY_AUTH_USERNAME |
No | Proxy username. |
PROXY_PASSWORD |
No | Proxy password. Set this in your .env file. Don’t hardcode it. |
PROXY_SSL_VERIFY |
No | true (default) or false. |
The admin password is required. Configure it as a Docker secret:
secrets:
nim_admin_password:
file: admin_password.txtNGINX Instance Manager supports two license operating modes, set with NIM_LICENSE_MODE_OF_OPERATION. A license isn’t required to deploy. You can activate it later in the UI.
Connected mode: NGINX Instance Manager contacts the NGINX licensing service directly over the internet. When the host has outbound HTTPS access, use this mode.
NIM_LICENSE_MODE_OF_OPERATION=connectedDisconnected mode: NGINX Instance Manager validates the license locally, without outbound internet access. Use this mode for air-gapped or restricted environments.
NIM_LICENSE_MODE_OF_OPERATION=disconnectedStop the stack without removing data:
docker compose -f docker-compose-rootless.yaml stopStop and remove containers and networks. Named volumes are preserved:
docker compose -f docker-compose-rootless.yaml downWarning: Data loss with docker compose down -vNever rundocker compose down -v. This destroys every named volume, including the database, certificates, and credentials.
docker exec nim-nim-1 nim-backupThis creates a backup at /data/backup/nim-backup-<date>.tgz inside the container. To find it on the host:
docker inspect volume nim_nim-data | jq '.[0].Mountpoint'
sudo ls -l /var/lib/docker/volumes/nim_nim-data/_data/backup- In
docker-compose-rootless.yaml, setNIM_MAINTENANCE: "true"and restart the stack. - Run the restore command:
docker exec nim-nim-1 nim-restore /data/backup/nim-backup-<date>.tgz- Set
NIM_MAINTENANCE: "false"and restart the stack again.
| Volume | Mount path | Purpose |
|---|---|---|
nim-data |
/data |
All NGINX Instance Manager persistent state: databases, encryption keys and secrets, streaming state, TLS certificates, and admin credentials, symlinked internally to their expected paths. |
nim-nap-compiler |
/opt/nms-nap-compiler |
F5 WAF for NGINX compiler artifacts. |
proxy-certs |
/usr/local/share/ca-certificates |
Custom CA certificates for outbound proxy connections. |
clickhouse-data |
/var/lib/clickhouse |
ClickHouse metrics and events database. |
To use NFS-backed volumes, add driver_opts to the volumes section in docker-compose-rootless.yaml:
volumes:
nim-data:
driver: local
driver_opts:
type: "nfs"
o: "addr=<NFS_IP>,rw"
device: ":/mnt/nfs_share/data"
clickhouse-data:
driver: local
driver_opts:
type: "nfs"
o: "addr=<NFS_IP>,rw"
device: ":/mnt/nfs_share/clickhouse"Get the logs and check the last line before exit:
docker compose -f docker-compose-rootless.yaml logs nim
docker compose -f docker-compose-rootless.yaml ps -a| Symptom | Fix |
|---|---|
admin_password.txt: no such file or directory |
Create the file: echo "<password>" > admin_password.txt |
Error: Clickhouse TTL value must be an integer |
Set NIM_METRICS_TTL, NIM_EVENTS_TTL, or NIM_SECURITY_TTL to a plain integer, for example "7". |
Error: NIM_WATCHDOG_TIMEOUT value must be an integer |
Set it to a plain integer in seconds, for example "60". |
Error: ENABLE_METRICS value must be either true or false |
Correct the value in your compose file. |
Process nms-<X> has stopped. Stopping container. |
See NMS service crashes. |
The image sets ownership on all NGINX Instance Manager paths to nms:nms at build time. A host volume owned by root overrides this.
Check ownership:
docker compose -f docker-compose-rootless.yaml exec nim ls -la /dataEvery entry must be owned by nms. If root owns the entries, someone pre-populated the volume as root before first boot.
- Named volumes (recommended): Don’t pre-create the directory as root. Docker assigns ownership to the first writer (
nms). - Bind mounts: Run
sudo chown -R 101:101 /path/to/nim-data. UID 101 is thenmsuser.
If startup loops on Waiting for ClickHouse...:
docker compose -f docker-compose-rootless.yaml ps clickhouse
docker compose -f docker-compose-rootless.yaml logs clickhouse
docker compose -f docker-compose-rootless.yaml exec nim nc -zv clickhouse 9000Both services must share the same Docker network.
Symptom: bind() to 0.0.0.0:443 failed (13: Permission denied), or a similar bind error.
Check whether another process on your host already uses the port:
sudo lsof -i :8443If it does, stop that process, or change the port mapping in docker-compose-rootless.yaml to an available port. Restart the stack to apply the change.
Symptom: a certificate error in your browser, or NGINX fails with TLS errors, on first access.
Verify the certificates exist:
docker compose -f docker-compose-rootless.yaml exec nim ls -la /data/certs/You should see manager-server.pem, manager-server.key, and ca.pem. Then verify the symlink:
docker compose -f docker-compose-rootless.yaml exec nim ls -la /etc/nms/certsThe expected output shows /etc/nms/certs -> /data/certs. If the symlink is missing, gather the container logs and contact F5 Support.
Symptom: Process nms-<X> has stopped. Stopping container.
The watchdog monitors nms-dpm, nms-core, nms-integrations, and nms-ingestion every 5 seconds. nms-sm (Security Monitor) isn’t part of the watchdog. It can crash, and the container keeps running.
Identify the crashing service:
docker compose -f docker-compose-rootless.yaml logs nim --tail=200 | grep -E "nms-(core|dpm|integrations|ingestion|sm)"- If
nms-ingestioncrashes, ClickHouse is likely unavailable. See ClickHouse not reachable. - If
nms-core,nms-dpm, ornms-integrationsrestarts repeatedly, restore from a recent backup. See Backup and restore.
Symptom: the license isn’t active, even though you set NIM_LICENSE.
Verify the license is valid base64:
echo "$NIM_LICENSE" | base64 -d | head -5Check for errors in the logs:
docker compose -f docker-compose-rootless.yaml logs nim | grep -i licenseIf activation fails, activate the license manually from the UI. For connected mode, confirm NGINX Console is reachable. See Proxy misconfiguration.
Symptom: outbound traffic (telemetry, license) fails when PROXY_ENABLE is set to true.
Confirm that the container applied the proxy settings:
docker compose -f docker-compose-rootless.yaml exec nim cat /etc/nms/nms.conf | grep -A 6 proxy_configTest proxy reachability from inside the container. A 200 or 400 response means the proxy is reachable. A 000 response means it isn’t.
docker compose -f docker-compose-rootless.yaml exec nim curl -x http://<PROXY_HOST>:<PROXY_PORT> -sS -o /dev/null -w "%{http_code}" https://product.connect.nginx.com/api/nginx-usage/batchIf your proxy uses a corporate CA, mount the PEM certificates into /usr/local/share/ca-certificates with the proxy-certs volume. For testing only, set PROXY_SSL_VERIFY=false.
Use maintenance mode to back up, restore, or debug a container that won’t start. In maintenance mode, the container initializes storage but doesn’t start NGINX Instance Manager services or NGINX.
To turn it on, set NIM_MAINTENANCE: "true" in docker-compose-rootless.yaml, then restart:
docker compose -f docker-compose-rootless.yaml up -dGet shell access:
docker compose -f docker-compose-rootless.yaml exec nim bashTo turn it off, remove NIM_MAINTENANCE or set it to false, then restart:
docker compose -f docker-compose-rootless.yaml restart nimdocker compose -f docker-compose-rootless.yaml logs --since 24h > nim-logs-$(date +%Y-%m-%d).txt
docker exec nim-nim-1 nim-backup- Deploy using Docker Compose: Standard, non-rootless Docker Compose deployment.