---
title: Deploy OpenLM Router
description: Run OpenLM Router, for OpenLM Platform, as a Docker Compose stack on a Linux host in your own network.
product: OpenLM Platform
---

To put the Router in your network, deploy it as a Docker Compose stack on a Linux host. When the stack is running, connect it to OpenLM Platform and define your failover pairs in [Configure OpenLM Router](https://www.openlm.com/documentation/cloud/deployment-operations/router/configuration).

## Before you begin

Confirm these prerequisites:

- A Linux host that meets the [system requirements](https://www.openlm.com/documentation/cloud/deployment-operations/router/system-requirements), with Docker Engine and the Docker Compose v2 plugin installed.
- Network access from the host to your OpenLM Platform and to each license server's `lmgrd` port.
- License servers that OpenLM Platform monitors through the OpenLM Broker, as described in [System requirements](https://www.openlm.com/documentation/cloud/deployment-operations/router/system-requirements#openlm-platform-prerequisites).
- The encryption key you generated in [System requirements](https://www.openlm.com/documentation/cloud/deployment-operations/router/system-requirements).

## Get the images

The Router images are published to the OpenLM public registry and pull anonymously, so you need no `docker login` and no pull secret. To pull the images, run these commands:

```bash
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>`. The registry has no `latest` or other moving tag, so pin an explicit tag on each image and record which builds you deployed. OpenLM releases the two images separately, so their tags can differ.

### Find the available builds

The tags on this page are the builds that this guide covers. 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`:

```bash
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:

```powershell
$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 most recent 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-router/`, create a file named `.env`:

```bash
# The MongoDB account the Compose file creates on the first start.
MONGO_USER=router
MONGO_PASSWORD=<your-generated-password>

# The key protecting the stored 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 development stack. Generate your own, as described in [System requirements](https://www.openlm.com/documentation/cloud/deployment-operations/router/system-requirements).

## Create the proxy file for the Router UI

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. Create `router-ui-proxy.conf` beside the `.env` file:

```nginx
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:

```yaml
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 each pair's listen port lands on a real host address that
    # workstations can reach, and so the license server sees the Router host as the
    # source. Listen ports are opened at runtime, after a pair is configured, so they
    # cannot be declared under `ports:` ahead of time.
    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 from the OpenLM Platform.
      - licOpsConfiguration__enabled=true
      - LICOPSCONFIGURATION_ENABLED=true
      # Required. Declares a failover Router. Fixed at the first start.
      - ROUTER_CONFIG_BOOTSTRAP_MODE=basic
      - 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 Router settings exactly as shown:

- `licOpsConfiguration__enabled` and `LICOPSCONFIGURATION_ENABLED` make the Router take its configuration from OpenLM Platform. Without them you cannot configure the Router for failover.
- `ROUTER_CONFIG_BOOTSTRAP_MODE=basic` declares that this Router serves failover pairs. When the Router connects, OpenLM Platform creates an empty starting configuration for it. Without this setting OpenLM Platform creates nothing, and the Router stays idle until a configuration exists.

The mode is fixed when the Router first starts. To change it, deploy a new Router with its own empty MongoDB database, and stop the old one.

:::warning[Use the exact variable names 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. The Router UI publishes its port on `127.0.0.1` too, on port `8102`, and the proxy serves it to browsers on port `8101`.

## Start the stack

To start the stack and follow the Router API log, run these commands:

```bash
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:

```text
Bootstrap seed skipped: the Router is service-managed (LicOps configuration enabled); identity RouterId <guid> established.
Router configuration shape: service-managed; ...
```

Confirm the Router is answering:

```bash
curl http://localhost:8100/api/v1/status
```

```json
{
  "status": "unconfigured",
  "configurationManagement": {
    "shape": "serviceManaged",
    "routerId": "a76db42e-5d48-497d-bbbb-f66dc04d9d5f",
    "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 has not been connected to OpenLM Platform, so it has no configuration and no client port is open. Check the `status` field. The Router receives its configuration from OpenLM Platform after you connect it.

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://<router-host>:8100/swagger`.

## Verify the deployment

Confirm the containers are running and the Router UI loads:

```bash
docker compose ps
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8101/router-management/
```

The second command prints `200`. Open `http://<router-host>:8101/router-management/` in a browser. A red notice that no Platform connection is configured 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.

:::note[Next step]
Continue to [Configure OpenLM Router](https://www.openlm.com/documentation/cloud/deployment-operations/router/configuration) to connect the Router to OpenLM Platform and define your first failover pair. To upgrade this deployment later, see [Change, upgrade, or remove OpenLM Router](https://www.openlm.com/documentation/cloud/deployment-operations/router/manage#upgrade-openlm-router).
:::

## Related

- [System requirements](https://www.openlm.com/documentation/cloud/deployment-operations/router/system-requirements)
- [Change, upgrade, or remove OpenLM Router](https://www.openlm.com/documentation/cloud/deployment-operations/router/manage)
- [Troubleshoot OpenLM Router](https://www.openlm.com/documentation/cloud/deployment-operations/router/troubleshooting#why-does-the-container-not-start)
