Deploy OpenLM LicOps
Deploy LicOps as a Docker Compose stack on a Linux host inside your network, reachable from your scheduler hosts. When the stack is running, connect it to OpenLM Platform in Configure OpenLM LicOps.
To run LicOps on Kubernetes instead, see Deploy on Kubernetes.
Before you begin
Confirm these prerequisites:
- A Linux host that meets the system requirements, with Docker Engine and the Docker Compose v2 plugin installed.
- Outbound access from the host to OpenLM Platform, and inbound access to port
8100from your scheduler hosts. - The encryption key you generated in System requirements.
Get the images
LicOps runs on the OpenLM Router images, which are published to the OpenLM public registry and pull anonymously. You need no docker login and no pull secret.
To pull the images, run these commands:
docker pull public.ecr.aws/r3q3q2f4/olm-routerapi:1.261006.0752
docker pull public.ecr.aws/r3q3q2f4/olm-routerui:1.261008.0932
Tags are build numbers in the form 1.<yymmdd>.<hhmm>. There is no latest or other moving tag, so pin an explicit one and record which build you deployed. The two images are released separately, so their tags can differ.
Find the newest build
To see every build that the registry holds for an image, list the image's tags. The registry issues the access token anonymously, so you need no account.
On Linux, with curl and jq, run these commands:
repo=olm-routerapi # or olm-routerui
token=$(curl -s "https://public.ecr.aws/token/?service=public.ecr.aws&scope=repository:r3q3q2f4/$repo:pull" | jq -r .token)
curl -s -H "Authorization: Bearer $token" "https://public.ecr.aws/v2/r3q3q2f4/$repo/tags/list" | jq -r '(.tags // [.errors[0].message])[]' | sort
In PowerShell, run these commands:
$repo = "olm-routerapi" # or olm-routerui
$token = (Invoke-RestMethod "https://public.ecr.aws/token/?service=public.ecr.aws&scope=repository:r3q3q2f4/${repo}:pull").token
(Invoke-RestMethod "https://public.ecr.aws/v2/r3q3q2f4/$repo/tags/list" -Headers @{ Authorization = "Bearer $token" }).tags | Sort-Object
Run the commands once for each image. The tags sort in build order, so the last line is the newest build. If the output is Rate exceeded, or PowerShell reports TOOMANYREQUESTS, the registry is limiting anonymous requests: wait 5 seconds and run the commands again.
Create the environment file
In a working directory on the host, for example ~/openlm-licops/, create a file named .env:
# The MongoDB account the Compose file creates on the first start.
MONGO_USER=router
MONGO_PASSWORD=<your-generated-password>
# The key protecting the stored OpenLM Platform client secret. Paste the string that
# `openssl rand -base64 32` printed, exactly as it printed it.
ADMISSION_PLATFORM_ENCRYPTION_KEY=<your-generated-key>
Generate the MongoDB password with openssl rand -hex 16. The password becomes part of a connection string, so a password with characters such as @, :, or / breaks the connection from the Router to MongoDB. MongoDB creates the account only when its data volume is empty, so changing the password later does not change the existing account.
Never reuse an encryption key from a sample or a test stack. Generate your own, as described in System requirements.
Create the proxy file for the Router UI
To serve the Router UI, create router-ui-proxy.conf beside the .env file. The Router UI is served under the /router-management/ path and expects a reverse proxy in front of it that strips that prefix and forwards API calls to the Router.
server {
listen 8101;
# The Router UI, with the /router-management prefix stripped.
location /router-management/ {
proxy_pass http://127.0.0.1:8102/;
}
# API calls from the browser, forwarded to the Router.
location /api/routerapi/ {
proxy_pass http://127.0.0.1:8100/api/;
}
}
Create the Compose file
Create docker-compose.yml in the same directory:
services:
mongo:
image: mongo:7
ports:
- "127.0.0.1:27017:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
volumes:
- mongo_data:/data/db
restart: unless-stopped
redis:
image: redis:7
command:
- redis-server
- --appendonly
- "yes"
- --appendfsync
- everysec
- --maxmemory-policy
- noeviction
ports:
- "127.0.0.1:6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
routerapi:
image: public.ecr.aws/r3q3q2f4/olm-routerapi:1.261006.0752
depends_on: [mongo, redis]
# Host networking, so the admission API and the LicOps listen port land on a
# real host address that your scheduler hosts and compute nodes can reach.
network_mode: host
environment:
- MONGODB__CONNECTIONSTRING=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@localhost:27017/?authSource=admin
- REDIS__CONNECTIONSTRING=localhost:6379,abortConnect=false
- ADMISSION_PLATFORM_ENCRYPTION_KEY=${ADMISSION_PLATFORM_ENCRYPTION_KEY}
# Required. The Router takes its configuration and rules from the OpenLM Platform.
- licOpsConfiguration__enabled=true
- LICOPSCONFIGURATION_ENABLED=true
# Required. Declares a LicOps Router. Fixed at the first start.
- ROUTER_CONFIG_BOOTSTRAP_MODE=loadBalancer
- ROUTER_CORRELATION_MODE=Ip
- ROUTER_PROXY_PROTOCOL_ENABLED=false
- ROUTER_TRUSTED_CIDR=127.0.0.1/32
# Keep the background-job dashboard off.
- hangfire__dashboard__enabled=false
restart: unless-stopped
routerui:
image: public.ecr.aws/r3q3q2f4/olm-routerui:1.261008.0932
ports:
- "127.0.0.1:8102:8101"
environment:
- LB_FQDN=_
restart: unless-stopped
routerui-proxy:
image: nginx:stable
depends_on: [routerui]
network_mode: host
volumes:
- ./router-ui-proxy.conf:/etc/nginx/conf.d/default.conf:ro
restart: unless-stopped
volumes:
mongo_data:
redis_data:
Keep the settings of the Router exactly as shown:
licOpsConfiguration__enabledandLICOPSCONFIGURATION_ENABLEDmake the Router take its configuration and admission rules from OpenLM Platform. Without them, nothing you define in LicOps Configuration reaches the Router.ROUTER_CONFIG_BOOTSTRAP_MODE=loadBalancermakes this Router a LicOps Router. When it connects, OpenLM Platform creates its starting LicOps configuration. Without this setting OpenLM Platform creates nothing, and the Router stays idle. The mode is fixed when the Router first starts, sobasichere would give you a failover Router instead. To change it, deploy a new Router with its own empty MongoDB database, and stop the old one.ROUTER_CORRELATION_MODE=IpandROUTER_PROXY_PROTOCOL_ENABLED=falsesuit scheduler adapters and direct API calls. These variables are required. Keep them exactly as shown.
MONGODB__CONNECTIONSTRING and REDIS__CONNECTIONSTRING take two underscores before CONNECTIONSTRING. The Router substitutes these names into its configuration at startup, so a misspelling is not merely ignored. The value lands empty and the container exits at boot with Mongo ConnectionString cannot be null or empty.
The connection strings use localhost because the Router uses host networking. MongoDB and Redis publish their ports on 127.0.0.1 only, so the Router reaches them on the host while nothing outside the host can. If you run MongoDB and Redis on servers you already operate, change the two connection strings to point at them.
Set the session timeouts
LicOps holds a seat for an admitted job until the job releases it, or until the session times out. Two timeouts decide when an unreleased seat goes back to the pool:
| Setting | Default | What it covers |
|---|---|---|
admissionController__reconcile__admitTtlSeconds | 900 (15 minutes) | How long an admitted job can wait before it starts. A job that starts later than this has lost its seat. |
admissionController__reconcile__dispatchTtlSeconds | 7200 (2 hours) | How long a started job keeps its seat without a release. |
Set the admit timeout longer than jobs typically wait in your queue. A job that starts after its admit timeout has lost its seat, and LicOps might already have admitted another job for it. Set the dispatch timeout longer than your longest licensed job, so a running job keeps its session until it ends. To change a timeout, add the setting to the routerapi environment, for example:
- admissionController__reconcile__admitTtlSeconds=3600
The timeouts are a safety net, not the normal path. A job that ends normally releases its seat at once.
Start the stack
To start the stack and follow the Router log, run these commands:
docker compose up -d
docker compose logs -f routerapi
On the first start, the Router creates its identity and logs that it takes its configuration from OpenLM Platform:
Bootstrap seed skipped: the Router is service-managed (LicOps configuration enabled); identity RouterId <guid> established.
Router configuration shape: service-managed; ...
In this setup you change the configuration and the rules in OpenLM Platform, not on the Router.
To confirm the Router is answering, run this command:
curl http://localhost:8100/api/v1/status
{
"status": "unconfigured",
"configurationManagement": {
"shape": "serviceManaged",
"routerId": "7c1e5b2a-3f4d-4e8a-9b6c-2d0f8a1e4c37",
"lastSync": {
"lastSuccessfulSync": null,
"lastFailure": "No Platform connection is configured, so the Router has no address for the LicOps Configuration service"
}
}
}
This is the correct result for a new Router. It is running but is not connected to OpenLM Platform, so it has no configuration and no pools. The configuration comes from OpenLM Platform, so connect the Router as described in Configure OpenLM LicOps.
The routerId is created once and stored in MongoDB. It is how OpenLM Platform tells this Router apart from others. Wiping the database creates a new one, which OpenLM Platform sees as a different Router.
The interactive API reference is at http://<licops-host>:8100/swagger.
Verify the deployment
To confirm the containers are running and the Router UI loads, run these commands:
docker compose ps
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8101/router-management/
The second command prints 200. Open http://<licops-host>:8101/router-management/ in a browser. The Router UI opens on Platform Connection, with a notice that no OpenLM Platform connection is configured. This is expected until you complete the next step.
The readiness endpoints of the Router, /readyz and /health/ready, answer 503 until the Router has collected its configuration from OpenLM Platform. On a new installation this is expected, not a fault.
Continue with Configure OpenLM LicOps to connect LicOps to OpenLM Platform.
Upgrade
Pin the new tag of each Router image in docker-compose.yml. To list the available builds, see Find the newest build. Then run these commands to recreate the containers:
docker compose pull routerapi routerui
docker compose up -d routerapi routerui
The configuration and rules live in OpenLM Platform, and the open sessions live in Redis, so they survive the replacement. While the Router restarts, admission requests fail: the scheduler adapters then apply the fail mode you chose, described in Connect workloads. For about 10 seconds after the restart, until the Router has caught up with the seat counts in OpenLM Platform, admit can answer Deny with the reason StaleSnapshot. The Slurm and LSF adapters treat that as a denial and reject the submission, so schedule upgrades for a quiet period.
The documentation team might draft this page using a large language model. A technical writer reviews every page and verifies it against the product before publishing it. A human makes the final decision.