# Environment variables

The MIKROSCOPE_* variables the CLI reads, the credentials that exist only as variables, and the envlist variables the agent reads on the router.

Source: https://jmrplens.github.io/mikroscope/reference/environment/

Two programs read environment variables, and they read different ones. The
**CLI** on your machine reads `MIKROSCOPE_*` variables as flag defaults, plus a
handful of credentials that have no flag, plus `GRAFANA_URL` and
`GRAFANA_TOKEN`. The **agent** on the router reads unprefixed variables
(`RATE_HZ`, `TOKEN` …) from its container envlist, which `install` writes. This
page lists both, from `cmd/mikroscope/*.go` and `internal/agent/config.go`.

## How the CLI reads them

- A variable sets a flag's **default**; the flag on the command line wins.
- A variable set to the empty string is treated as unset.
- The CLI never reads a `.env` file. Export it into the shell first:

  ```sh
  set -a; . ./.env; set +a
  ```

- Quote a URL that holds `&` when it lives in a file you `source`:
  unquoted, `&` is a shell operator. `.env.example` quotes
  `MIKROSCOPE_INFLUX_URL` for that reason.

Most deployment flags have **no** variable. `--rate`, `--buffer`, `--port`,
`--memory-max`, `--mem-limit-mb`, `--capture-mb`, `--triggers`, `--floor-hz`,
`--privileged`, `--ephemeral` and `--expose` are set on the command line or not
at all. [Commands and flags](/mikroscope/reference/cli/) has every flag.

## Router and deployment

| Variable                  | Flag             | Read by                                               | Default in the code          | Value in `.env.example` |
| ------------------------- | ---------------- | ----------------------------------------------------- | ---------------------------- | ----------------------- |
| `MIKROSCOPE_ROUTER`       | `--router`       | `doctor`, `install`, `upgrade`, `uninstall`, `status` | none (required)              | empty                   |
| `MIKROSCOPE_SSH_PORT`     | `--ssh-port`     | the same                                              | ssh config                   | `22`                    |
| `MIKROSCOPE_SSH_KEY`      | `--ssh-key`      | the same                                              | ssh agent or config          | empty                   |
| `MIKROSCOPE_ARCH`         | `--arch`         | the deployment verbs                                  | `arm64`                      | `arm64`                 |
| `MIKROSCOPE_AGENT_TAR`    | `--agent-tar`    | `plan`, `install`, `upgrade`, `image`                 | empty (build the agent here) | not in the file         |
| `MIKROSCOPE_REMOTE_IMAGE` | `--remote-image` | `plan`, `install`, `upgrade`                          | empty (upload a tar)         | not in the file         |
| `MIKROSCOPE_NAME`         | `--name`         | the deployment verbs                                  | `mikroscope`                 | commented out           |
| `MIKROSCOPE_VETH`         | `--veth`         | the deployment verbs                                  | `veth-mikroscope`            | `veth-mikroscope`       |
| `MIKROSCOPE_SUBNET`       | `--subnet`       | the deployment verbs, `record`, `forward`             | `172.30.10.0/30`             | `172.30.10.0/30`        |
| `MIKROSCOPE_IFACE_LIST`   | `--iface-list`   | the deployment verbs                                  | `LAN`                        | `LAN`                   |
| `MIKROSCOPE_ADDR_LIST`    | `--addr-list`    | the deployment verbs                                  | `LANs`                       | `LANs`                  |
| `MIKROSCOPE_DISK`         | `--disk`         | the deployment verbs                                  | empty (internal flash)       | commented out, `tmpfs`  |
| `MIKROSCOPE_LAN_ADDRESS`  | `--lan-address`  | the deployment verbs                                  | empty                        | commented out           |
| `MIKROSCOPE_TOKEN`        | `--token`        | the deployment verbs, `record`, `forward`             | empty                        | commented out           |

`MIKROSCOPE_TOKEN` is two things at once. For `install` it is the token
written into the agent's envlist, which the agent then requires. For `record`
and `forward` it is the token the direct transport sends. The envlist is not
a secret store: any RouterOS user with the `read` policy can list every
container's envlist over the API (verified on RB5009UG+S+, RouterOS 7.24.2, 2026-09-11), which is why the token
is the only credential that goes there.

## Reaching the agent and the RouterOS API

| Variable                  | Flag           | Read by                     | Meaning                                                                                            |
| ------------------------- | -------------- | --------------------------- | -------------------------------------------------------------------------------------------------- |
| `MIKROSCOPE_API_ADDR`     | `--api`        | `record`, `mark`, `forward` | RouterOS binary API `host:port`; `.env.example` has `192.168.88.1:8728`                            |
| `MIKROSCOPE_API_USER`     | `--api-user`   | `record`, `mark`, `forward` | the dedicated API user                                                                             |
| `MIKROSCOPE_API_PASSWORD` | none           | `record`, `mark`, `forward` | that user's password; there is deliberately no flag                                                |
| `MIKROSCOPE_INTERFACES`   | `--interfaces` | `forward`                   | comma-separated interfaces for `monitor-traffic`; `.env.example` has `bridge,ether1` commented out |

The relay, `--log-markers` and the API tier fail, or in `forward`'s case are
disabled with a warning, unless `--api`, `--api-user` and
`MIKROSCOPE_API_PASSWORD` are all set. [The API
user](/mikroscope/security/api-user/) has the policy that user needs.

## Sinks

| Variable                   | Flag            | Default in the code | Meaning                                                                          |
| -------------------------- | --------------- | ------------------- | -------------------------------------------------------------------------------- |
| `MIKROSCOPE_INFLUX_URL`    | `--influx`      | empty               | InfluxDB 3 write URL                                                             |
| `MIKROSCOPE_LOKI_URL`      | `--loki`        | empty               | Loki push URL                                                                    |
| `MIKROSCOPE_LOKI_TENANT`   | `--loki-tenant` | empty               | `X-Scope-OrgID`                                                                  |
| `MIKROSCOPE_OTLP_URL`      | `--otlp`        | empty               | OTLP/HTTP metrics endpoint                                                       |
| `MIKROSCOPE_GRAPHITE_ADDR` | `--graphite`    | empty               | carbon plaintext `host:port`                                                     |
| `MIKROSCOPE_ELASTIC_URL`   | `--elastic`     | empty               | Elasticsearch or OpenSearch base URL                                             |
| `MIKROSCOPE_TELEGRAF_URL`  | `--telegraf`    | empty               | Telegraf listener URL (`http://`, `tcp://` or `udp://`)                          |
| `MIKROSCOPE_HOST_TAG`      | `--host-tag`    | `router`            | host tag on every point in every sink; `.env.example` has `rb5009` commented out |

Setting a sink's URL variable is enough to turn that sink on: `forward` builds
every sink whose flag ends up non-empty, from the command line or from the
variable. Every one of these names carries the `MIKROSCOPE_` prefix: a bare
`LOKI_URL` is read by nothing, and `.env.example` says so beside
`MIKROSCOPE_LOKI_URL`.

## Credentials that have no flag

A flag is visible in `ps` and in a shell history, so every secret the CLI uses
is read from the environment only.

| Variable                    | Used for                                          |
| --------------------------- | ------------------------------------------------- |
| `MIKROSCOPE_API_PASSWORD`   | the RouterOS API user                             |
| `MIKROSCOPE_INFLUX_TOKEN`   | InfluxDB, sent as `Authorization: Bearer <token>` |
| `MIKROSCOPE_LOKI_TOKEN`     | Loki                                              |
| `MIKROSCOPE_OTLP_TOKEN`     | the OTLP endpoint                                 |
| `MIKROSCOPE_ELASTIC_AUTH`   | Elasticsearch or OpenSearch                       |
| `MIKROSCOPE_TELEGRAF_TOKEN` | the Telegraf HTTP listener                        |
| `GRAFANA_TOKEN`             | `dashboards import` and `dashboards check`        |

## Grafana

`dashboards import` and `dashboards check` read `GRAFANA_URL` (the default of
`--grafana`) and `GRAFANA_TOKEN`. Neither has the `MIKROSCOPE_` prefix.
`.env.example` sets `GRAFANA_URL` twice, once empty in its LAN-services block
and once commented out in the Grafana block, and says the token never goes in
the file.

## The agent's envlist

The agent reads these from its container's environment when it starts. An
invalid value makes it print one line starting
`mikroscope-agent: bad configuration:`, which RouterOS puts in its log under
the `container` topic, and exit with status 2.

| Variable               | Agent default                                      | Accepted                    | Meaning                                                                                                                                     |
| ---------------------- | -------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `RATE_HZ`              | `10`                                               | 1–100                       | sampler rate                                                                                                                                |
| `BUFFER_S`             | `300`                                              | 10–3600                     | ring length in seconds                                                                                                                      |
| `PORT`                 | `9123`                                             | 1–65535                     | HTTP port                                                                                                                                   |
| `ADDR`                 | empty                                              | an address                  | bind address. Empty binds `:PORT`, every address in the container's network namespace; `install` always sets it to the agent's veth address |
| `TOKEN`                | empty                                              |                             | when set, every path but `/healthz` requires `Authorization: Bearer <token>`                                                                |
| `IRQ_TOP_K`            | `8`                                                | 0–64                        | interrupt lines kept per sample, busiest first; `0` ships no interrupt lines and no `irq_total`                                             |
| `MEM_LIMIT_MB`         | `14`                                               | 8–1024                      | Go soft memory limit in MiB                                                                                                                 |
| `FLOOR_HZ`             | `0`                                                | 0–1000                      | `0` keeps the per-source floors; N puts every level source on N Hz and turns off emit-on-change                                             |
| `SOURCES`              | empty (every source detected)                      | comma-separated names       | restrict the optional sources to this list; `/proc/stat` is always read                                                                     |
| `TRIGGERS`             | `softnet-drop,oom,kmsg<=3,reset,irq-err,flash-bad` | conditions, comma-separated | capture trigger conditions                                                                                                                  |
| `CAPTURE_MB`           | `4`                                                | 0–256                       | pinned-bytes budget for captures; `0` turns them off                                                                                        |
| `CAPTURE_PRE_S`        | `5`                                                | 1–60                        | seconds kept before the sample a trigger fires on                                                                                           |
| `CAPTURE_POST_S`       | `5`                                                | 1–60                        | seconds kept after it                                                                                                                       |
| `CAPTURE_POLICY`       | `first`                                            | `first` or `last`           | on a full budget: `first` refuses the new capture, `last` evicts the oldest                                                                 |
| `TRIGGER_REFRACTORY_S` | `10`                                               | 0–3600                      | quiet time per condition after it fires                                                                                                     |
| `PROC_ROOT`            | `/proc`                                            | a path                      | where `/proc` is read from; the kernel log is `/dev/kmsg` only when this is `/proc`, otherwise `dev/kmsg` beside the given directory        |
| `SYS_ROOT`             | `/sys`                                             | a path                      | where `/sys` is read from                                                                                                                   |

The names `SOURCES` accepts are the optional sources `/capabilities` reports:
`meminfo`, `loadavg`, `softnet`, `softirqs`, `interrupts`, `vmstat`, `psi`,
`schedstat`, `self`, `buddyinfo`, `yaffs`, `diskstats`, `slabinfo`, `kmsg`,
`thermal`, `cpufreq`, `mtd` and `perf`. The trigger conditions and what each
compares are on [triggered capture](/mikroscope/record/triggers/).

Before it listens, the agent checks the ring against the container's own
`memory.max`: `RATE_HZ × BUFFER_S × 2 560 B` plus `CAPTURE_MB` must fit, or it
refuses to start and names the three variables and `--memory-max`. If that
figure is more than half of `MEM_LIMIT_MB`, it starts and logs a warning with
the limit to raise to. 2 560 B is not itself a measurement: it is the mean
line measured on the RB5009 with every source of that date (2 439 B, 4 cores,
`IRQ_TOP_K` 8, RouterOS 7.24.2, 2026-09-12) rounded up. A board with more cores
or more interrupt lines writes longer lines, so the check errs open. When the
agent cannot read its `memory.max`, the refusal check does not run.

### What install writes into it

`install` and `upgrade` write the envlist `<name>-env` with these entries, in
this order, and nothing else:

The entries install writes into the agent's envlist:

| Key | Written | From | Holds |
| --- | --- | --- | --- |
| `MIKROSCOPE_TAG` | always | `--name` | the ownership marker `mikroscope:<name> (managed by mikroscope)`, written first and removed last; the agent ignores it |
| `RATE_HZ` | always | `--rate`, default `10`, 1–100 | the sampler rate, in Hz |
| `BUFFER_S` | always | `--buffer`, default `300`, 10–3600 | the ring's length, in seconds |
| `PORT` | always | `--port`, default `9123`, 1–65535 | the agent's HTTP port |
| `ADDR` | always | `--subnet` | the agent's address, the `.2` of the /30; the agent binds only there |
| `MEM_LIMIT_MB` | always | `--mem-limit-mb`, default `40`, 8–1024 | the agent's Go soft memory limit, in MiB |
| `FLOOR_HZ` | only when above 0 | `--floor-hz`, default `0`, 0–1000 | one cadence for every level source, in Hz |
| `CAPTURE_MB` | always | `--capture-mb`, default `4`, 0–256 | the triggered-capture budget, in MiB; `0` turns captures off |
| `TRIGGERS` | only when set | `--triggers` | the trigger conditions; unset, the agent uses its default set |
| `TOKEN` | only when set | `--token` | the bearer token the agent requires, from `--token` or `MIKROSCOPE_TOKEN`, with or without `--expose` |

> **The agent's defaults and install's defaults are not the same**
>
> An agent started with no envlist entry uses a 14 MiB soft memory limit. `install` always writes
> `MEM_LIMIT_MB=40`, and the container's `memory-max` is `64M`, because a 300 s ring at 10 Hz holds
> about 7.3 MB and a 14 MiB limit kept the garbage collector running continuously on the RB5009: 9.38 % of one core against 1.39 % with room, both with the ring full, measured
> on RouterOS 7.24.2 on 2026-09-12. `IRQ_TOP_K`, `CAPTURE_PRE_S`, `CAPTURE_POST_S`, `CAPTURE_POLICY`,
> `TRIGGER_REFRACTORY_S`, `SOURCES`, `PROC_ROOT` and `SYS_ROOT` have no flag, so an installed agent
> runs with their defaults.

## In .env.example but not read by the code

`.env.example` is also the development template for the project's own devices,
so it carries names that no binary reads. Setting them changes nothing:

- `MIKROSCOPE_ROUTEROS`, the RouterOS version of the reference device.
- `OPERATOR_HOST_IP`, the operator host for push-sink and probe tests.
- The `HEXS_*` block (`HEXS_ROUTER`, `HEXS_SSH_PORT`, `HEXS_SSH_KEY`,
  `HEXS_ARCH`, `HEXS_API_ADDR`, `HEXS_API_USER`, `HEXS_API_PASSWORD`), kept
  for a hEX S that has not arrived.

## See also

- [Commands and flags](/mikroscope/reference/cli/): every flag, including the ones no variable
  sets.
- [The API user](/mikroscope/security/api-user/): the RouterOS user behind `MIKROSCOPE_API_USER`.
- [Each source at its own floor](/mikroscope/limits/source-floors/): what `FLOOR_HZ` overrides.
- [The cost of the observer](/mikroscope/cost/): why the memory limit is sized to the ring.
