Deploy rootless using Docker Compose
This guide shows you how to deploy F5 NGINX Instance Manager using Docker Compose in a rootless configuration. In this setup, all container processes run as a non-root user (nms), following the principle of least privilege for production environments.
A key capability of this deployment is runtime configuration injection: you can change NGINX Instance Manager settings by editing environment variable files and restarting the stack. You don’t need to rebuild the Docker image.
Key characteristics of this deployment:
- Rootless by design — All processes run as
nms(non-root), reducing the attack surface and satisfying security hardening requirements. - Runtime configuration — Change NGINX Instance Manager settings by updating
.envanddocker-compose.yaml. You don’t need to rebuild the image. - Production-hardened — Startup scripts are idempotent and avoid fragile patterns such as recursive permission changes.
- Flexible licensing — Supports both connected and disconnected license modes, switchable at runtime.
Before you begin, make sure you have the following:
- Docker Engine 20.10 or later installed on a Linux host (amd64 or arm64).
- Docker Compose plugin v2 or later.
- A valid NGINX Instance Manager license file, base64-encoded.
- An NGINX certificate and private key, required to download NGINX Instance Manager packages during image build.
- Basic familiarity with Docker Compose and YAML syntax.
Clone the deployment repository to your host machine:
git clone https://github.com/nginx/nginx-demos.git
cd nginx-demos/nginx-instance-manager/docker-deploymentRun the build script, supplying your NGINX certificate, key, and a name for the resulting image:
./build.sh -C <NGINX_CERTIFICATE> -K <NGINX_CERTIFICATE_KEY> -t <IMAGE_NAME>Replace the placeholders:
<NGINX_CERTIFICATE>— Path to your NGINX TLS certificate file.<NGINX_CERTIFICATE_KEY>— Path to your NGINX TLS private key file.<IMAGE_NAME>— Your preferred Docker image tag (for example,nim:latest).
The build script downloads NGINX Instance Manager packages from the NGINX repository using your certificate and key, and produces a self-contained rootless image.
Edit docker-compose/.env and set the following values:
NIM_IMAGE=<IMAGE_NAME>
NIM_LICENSE=<BASE64_ENCODED_LICENSE_FILE>
NIM_USERNAME=admin
NIM_PASSWORD=nimadmin
NIM_CLICKHOUSE_ADDRESSPORT=docker-compose-clickhouse-1:9000
NIM_CLICKHOUSE_USERNAME=<username>
NIM_CLICKHOUSE_PASSWORD=<password>
NIM_LICENSE_MODE_OF_OPERATION=connectedReplace <IMAGE_NAME> with the tag used in the build step, and <BASE64_ENCODED_LICENSE_FILE> with your base64-encoded NGINX Instance Manager license. Set NIM_LICENSE_MODE_OF_OPERATION to connected or disconnected depending on your environment (see License modes below).
ChangeNIM_USERNAMEandNIM_PASSWORDto values appropriate for your environment before deploying to production.
Open docker-compose.yaml and confirm that the nim service passes all required variables into the container:
environment:
- NIM_LICENSE=${NIM_LICENSE}
- NIM_USERNAME=${NIM_USERNAME}
- NIM_PASSWORD=${NIM_PASSWORD}
- NIM_CLICKHOUSE_ADDRESSPORT=${NIM_CLICKHOUSE_ADDRESSPORT}
- NIM_CLICKHOUSE_USERNAME=${NIM_CLICKHOUSE_USERNAME}
- NIM_CLICKHOUSE_PASSWORD=${NIM_CLICKHOUSE_PASSWORD}
- NIM_LICENSE_MODE_OF_OPERATION=${NIM_LICENSE_MODE_OF_OPERATION}If you add new configuration variables later, add a corresponding line here to make the value available inside the container.
Start NGINX Instance Manager and its dependencies (including ClickHouse) with:
docker compose -f docker-compose.yaml up -dWhen the containers are running, open a browser and go to:
https://localhost/Accept the self-signed TLS certificate warning if prompted. Log in using the NIM_USERNAME and NIM_PASSWORD values you set in .env.
When the NGINX Instance Manager container starts, startNIM.sh reads the environment variables that Docker Compose passes in. It writes their values into the NGINX Instance Manager configuration files using yq, a YAML processor. The two configuration files are:
/etc/nms/nms.conf/etc/nms/nms-sm-conf.yaml
This means NGINX Instance Manager applies fresh configuration on every container start. You change a setting by updating the variable in .env and restarting the stack. You don’t need to rebuild the image.
The script exposes two helper functions:
set_nms_conf— writes a value to a key path innms.conf.set_nms_sm— writes a value to a key path innms-sm-conf.yaml.
To inject a new or changed configuration value, complete these steps:
-
Add the variable to
.envMY_NEW_SETTING=myvalue -
Pass the variable into the container in
docker-compose.yamlyaml environment: - MY_NEW_SETTING=${MY_NEW_SETTING} -
Map the variable to a config key in
startNIM.shshell # For nms.conf: set_nms_conf '.path.to.config.key' MY_NEW_SETTING # For nms-sm-conf.yaml: set_nms_sm '.path.to.config.key' MY_NEW_SETTING
Then restart the stack to apply the change:
docker compose -f docker-compose.yaml up -dYou don’t need to rebuild the image.
NGINX Instance Manager supports two license operating modes. The correct choice depends on whether your host has outbound internet access to the NGINX licensing service.
NGINX Instance Manager contacts the NGINX licensing service directly over the internet. Use this mode when the host has reliable outbound HTTPS access.
Set in .env:
NIM_LICENSE_MODE_OF_OPERATION=connectedNGINX Instance Manager operates without outbound internet access and validates the license locally. Use this mode for air-gapped or restricted environments.
Set in .env:
NIM_LICENSE_MODE_OF_OPERATION=disconnectedTo change the license mode without rebuilding the image:
-
Update
NIM_LICENSE_MODE_OF_OPERATIONindocker-compose/.env. -
Confirm the variable is listed under
environment:in thenimservice indocker-compose.yaml. -
Confirm the mapping exists in
startNIM.sh:set_nms_conf '.integrations.license.mode_of_operation' NIM_LICENSE_MODE_OF_OPERATION -
Restart the stack:
docker compose -f docker-compose.yaml up -d
To stop the running stack:
docker compose -f docker-compose.yaml stopTo stop and remove containers and networks:
docker compose -f docker-compose.yaml downAll processes run as the nms non-root user. If you see permission errors, check that any host-mounted volumes are readable and writable by the nms user. Avoid using recursive chown or chmod commands, because these can interfere with rootless operation.
If an updated variable has no effect after a restart, verify all three steps were completed:
- The variable is defined in
.envwith the correct value. - The variable is listed under
environment:in thenimservice indocker-compose.yaml. - A
set_nms_conforset_nms_smcall for that variable exists instartNIM.sh. - The stack was fully restarted (
docker compose up -dre-creates containers on config change).
To inspect startup output and verify configuration injection ran successfully:
docker compose -f docker-compose.yaml logs nim
docker compose -f docker-compose.yaml logs clickhousedocker compose -f docker-compose.yaml ps- Deploy using Docker Compose — Standard (non-rootless) Docker Compose deployment.
- NGINX Instance Manager Documentation
- yq YAML Processor
- Docker Compose Documentation