Deploy rootless using Docker Compose
This guide shows you how to deploy F5 NGINX Instance Manager (NIM) 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: NIM settings can be changed by editing environment variable files and restarting the stack. No Docker image rebuild is required.
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 — Modify NIM settings by updating
.envanddocker-compose.yaml. No image rebuild needed. - 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 NIM 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/nullchacha/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 NIM 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=default
NIM_CLICKHOUSE_PASSWORD=NGINXr0cks
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 NIM 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.
Launch NIM and its dependencies (including ClickHouse) with:
docker compose -f docker-compose.yaml up -dOnce 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 NIM container starts, the entrypoint script startNIM.sh reads environment variables passed in by Docker Compose and writes their values directly into the NIM configuration files using yq, a YAML processor. The two configuration files are:
/etc/nms/nms.conf/etc/nms/nms-sm-conf.yaml
This means configuration is applied fresh on every container start. You change a setting by updating the variable in .env and restarting the stack—no image rebuild is required.
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 modified configuration value, complete three 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 -dNo image rebuild is needed.
NIM supports two license operating modes. The correct choice depends on whether your host has outbound internet access to the NGINX licensing service.
NIM 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=connectedNIM 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, as 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