Requirements
Three things on the router must be in place before install can write anything, and the tool
cannot do them for you. mikroscope doctor checks every requirement read-only, in one ssh connect,
and prints the command or the physical step that fixes each one that is missing.
| Requirement | Value | doctor prints |
|---|---|---|
| RouterOS | 7.24 or later | RouterOS 7.24 or later |
| Architecture | arm64, arm or x86_64 |
architecture has a container package |
container package |
installed and enabled | container package installed and enabled |
| Device mode | container=yes |
device-mode container=yes |
| Free memory | at least --memory-max, 64 MiB by default |
free memory ≥ <size> |
| Free storage | the extracted root, plus the tar on the tar route | free flash ≥ <size> (…) |
| Interface list | the one --iface-list names, LAN by default |
interface list <list> exists |
| ssh | an admin user, with a key or an ssh agent | none; the connect itself |
Scroll sideways to see every column
Router
Section titled “Router”The router must run RouterOS 7.24 or later on arm64, arm (32-bit RouterOS) or x86_64. MIPS, TILE
and PPC routers have no container package.
7.24 is the floor because the container step writes privileged=, an attribute RouterOS
added in that release (“container - added
ability to run containers in privileged mode”). --privileged=false changes the value written, not
whether it is written. doctor marks an earlier release MISSING, and the install script stops
there with nothing written. Privileged mode has what the
attribute changes.
install reads the architecture from the router (--arch auto, the default), and each route
takes the agent build that matches:
| Your device | architecture-name |
--arch |
Agent build |
|---|---|---|---|
| RB5009, CCR2004, hAP ax³ and other 64-bit ARM | arm64 |
arm64 |
linux/arm64 |
| hEX Refresh / hEX S (2025), any EN7562CT board | arm |
arm |
linux/arm/v5 |
| Other 32-bit ARM (hAP ac², hAP ax², …) | arm |
arm |
linux/arm/v5 or v7 |
| CHR and x86 RouterOS | x86_64 |
amd64 |
linux/amd64 |
Scroll sideways to see every column
32-bit ARM is two things, not one. MikroTik’s
container documentation
states that devices with the EN7562CT CPU support only arm32v5 container images, while its other
32-bit ARM boards run an ARMv7 userland. An ARMv5 binary runs on both;
an ARMv7 one does not run on the first, and it fails as an exec format error in the container log
after a successful install. So --goarm defaults to 5, the level that starts everywhere, and
the image declares the matching variant. --goarm 7 builds the ARMv7 one for a board where that
instruction set is wanted.
With --remote-image the router picks the build itself: the published index carries linux/amd64,
linux/arm64, linux/arm/v7 and linux/arm/v5. On arm, which of the two 32-bit builds RouterOS
pulls is not known, so doctor warns there; if the container stops with Exec format error,
install from the ARMv5 tar (Offline install).
Container package
Section titled “Container package”Download the container package for your architecture and RouterOS version from mikrotik.com,
upload it to the router and reboot. If it is there and disabled, run
/system/ and reboot. doctor counts the package only when it is installed
and not disabled. To read it on the router:
/system/package/print where name="container"Device mode
Section titled “Device mode”-
Run, on the router’s terminal:
/system/device-mode/update container=yes -
RouterOS answers with the step it waits for. A router says:
update: please activate by turning power off or pressing reset or mode button in 5m00sCHR says
update: turn off power in 5m to activate changes. -
Within those five minutes, press the reset or mode button, or cut the power. On CHR, power the virtual machine off and on. If nobody does, the change is cancelled.
After three failed attempts the router says too many unsuccessful attempts … to reset attempt-count and needs a power cycle, or a press of the reset or mode button, before it accepts
another. /system/ shows container: yes once it is done.
Preflight checks
Section titled “Preflight checks”doctor prints device: with the board, the RouterOS version and the architecture, then one line
per check marked ok, MISSING or WARN, with what it found in parentheses and, for a missing one
or a warning, a fix: line. It ends with doctor: every prerequisite is met, or fails with
N prerequisite(s) missing; nothing was written. A WARN is advice: it never changes that ending,
and install goes ahead past it. install runs the same checks first unless you pass --no-doctor.
| Check, as printed | Passes when | The fix it names |
|---|---|---|
| RouterOS 7.24 or later | /system/resource reports a version of 7.24 or later, read with or without a patch number and whatever the channel, as in 7.24 (stable) or 7.25rc1 (testing) | upgrade RouterOS to 7.24 or later (/system/package/update), and the container package with it |
| architecture has a container package | the router's architecture-name is arm, arm64 or x86_64, the architectures MikroTik publishes a container package for | none: no agent can run on this router |
| the router picks the image's architecture | with --remote-image: always, naming the router's architecture, because RouterOS picks it from the image's multi-architecture index. A warning on arm, where the index holds both linux/arm/v5 and linux/arm/v7 and which one RouterOS pulls is not known | on arm, if the container stops with Exec format error, install from mikroscope-agent-armv5.tar with --agent-tar |
| architecture matches the --agent-tar image | with --agent-tar: the tar's own architecture is the router's (amd64 for x86_64); --arch is not needed | download the release asset it names, mikroscope-agent-<arch>.tar |
| architecture read from the router | with neither image flag and --arch unset (or auto): always; install and upgrade build or load the image for the architecture doctor read | none |
| architecture matches --arch <arch> | with an explicit --arch and neither image flag: the router's architecture-name is the one --arch maps to (arm64, arm, x86_64) | re-run with the --arch it names, or leave --arch out so that install reads it from the router |
| container package installed and enabled | a container package exists with disabled=no | download the container package for this architecture and RouterOS version, upload it and reboot; when it is there and disabled, /system/ and reboot |
| device-mode container=yes | /system/device-mode reports container=yes | /system/, then confirm it as the console asks: on a router that says update: please activate by turning power off or pressing reset or mode button, press the reset or mode button or cut the power; on CHR, which says update: turn off power in 5m to activate changes, power the VM off and on again within 5 minutes |
| free memory ≥ <--memory-max> | free-memory is at least what --memory-max asks for, 64 MiB by default | free memory on the router, or ask for less with --memory-max |
| free memory leaves room for the pull | a warning, with --remote-image only: free-memory is at least --memory-max plus 16 MiB, room for RouterOS to pull and extract the image before the agent starts. How much a pull takes is not measured, so the margin is an estimate | install from a tar with --agent-tar if the pull fails |
| free flash ≥ <size> (image tar + extracted root) | without --disk or --ephemeral: free-hdd-space is at least twice the image plus 4 MiB. With --remote-image nothing is uploaded and the name ends in (extracted root): the root the pulled image is extracted into, 7 MiB, plus 4 MiB | free flash, or install with --disk tmpfs or --ephemeral where a tmpfs disk exists |
| disk <disk> exists | with --disk or --ephemeral: a disk with that slot exists | /disk/add type=tmpfs tmpfs-max-size=64M slot=tmpfs for a RAM disk, or name an existing disk with --disk |
| disk <disk> has ≥ <size> free (image tar + extracted root) | with --disk or --ephemeral, once the disk exists: its free space is at least twice the image plus 4 MiB; with --remote-image, the 7 MiB root plus 4 MiB, as the flash check | free space on that disk, or give a tmpfs disk a larger tmpfs-max-size |
| disk tmpfs is RAM | with --ephemeral: the disk in slot tmpfs is of type tmpfs | free the slot for a tmpfs disk, or install with --disk <slot> without --ephemeral |
| start-on-boot suits a root in RAM | a warning, when the disk is a tmpfs disk: start-on-boot resolves to no, since a reboot empties the disk and a container started at boot has no root | pass --start-on-boot no, or --ephemeral |
| veth name <veth> is free or ours | no veth has that name, or the one that has it carries this install's tag | pick another --veth (and --subnet), or remove the veth by hand if it is a leftover of yours |
| envlist <name>-env is free or ours | no envlist has that name, or the one that has it holds this install's MIKROSCOPE_TAG entry | pick another --name |
| install manifest <disk/>mikroscope/<name>.manifest.txt is free or ours | no file is at the install manifest's path, or the one there holds this install's tag= line | move the file away, or pick another --name |
| container name <name> is free or ours | with --container-name: no container has that name, or the one that has it carries this install's tag | pick another --container-name |
| subnet <subnet> does not overlap a route | no route of the main table, active or not, lies inside the /30, and no connected network on another interface holds its router end. Routes that only contain the /30 (a default route, a wider prefix to a VPN), blackhole routes and the install's own veth are left out | pick another /30 with --subnet |
| interface list <list> exists | the --iface-list list (default LAN) exists. With --iface-list none doctor prints interface list the veth joins and passes: no membership is written | --iface-list none when no firewall rule needs the veth in a list (doctor offers it first then); otherwise /interface/, or pass the list your in-interface-list=!… drop rule uses |
| address list <list> | always: install adds the /30 to the --addr-list list (default LANs), which creates it when it is missing, and uninstall removes the entry. With --addr-list none doctor prints address list the /30 joins. Whether a rule needs the membership is the next row's question | none |
| no firewall rule drops the agent's replies | doctor reads every enabled rule of the chains the agent's replies meet, /ip/firewall/raw prerouting and /ip/firewall/filter forward and input, and walks each one as RouterOS does, first match wins, with the replies in the lists the plan joins: no rule drops them. A warning when a rule might, because it matches on something doctor does not judge (a destination, a mark, a rate), and when the replies to a LAN host pass but a rule may drop the ones to the router itself (filter input), which the relay transport needs | the --iface-list and --addr-list that let the replies through; when no list does (a src-address=!<range> rule, say), add an accept rule for in-interface=<veth> before that rule, or pick a --subnet inside the range |
| no firewall rule doctor reads is invalid or names a deleted list | a warning: no rule of raw prerouting or filter forward and input is marked invalid, which RouterOS passes over as if it were not there (it names an interface that was removed or is not ready, and about says which), and none names an interface list that was removed, which RouterOS keeps as the list's id (in-interface-list=!*2000010) and matches as an empty list. A rule changed a moment before reads invalid with no reason until RouterOS has applied it | fix or remove what an invalid rule names; set a deleted list again by name, since creating a list of the same name does not repair the rule. With no reason given, run doctor again |
| --lan-address <address> is the router's | with --expose: an interface of the router holds that address | pass the address the router has on its LAN, as /ip/address/print lists it |
| --lan-address is not on the uplink | a warning, with --expose: the interface that holds the address carries no default route, is in no WAN list, and shares no interface list with the interface that carries the default route | pass the router's LAN address: on the uplink the dst-nat would publish the agent on the Internet side |
| no registry credential meant for another registry | a warning, with --remote-image only: no /container/config username is set, or the host of registry-url is the host the image is pulled from, every spelling of Docker Hub counted as one. An empty registry-url with a username set warns. Doctor reads whether a username is set, never the name, and cannot read the password | /container/config holds one username for the whole device, and a credential from another registry can make the pull of a public image end in auth error. Install from a tar with --agent-tar, pass a --remote-image on the registry the username belongs to, or clear the username if nothing else needs it |
| the installed agent published on the LAN asks for a token | a warning, shown only when an install of this --name has a dst-nat on the LAN: its environment holds a TOKEN. Doctor counts the entries, never reads the value | upgrade with the same --name and --token <secret>; or remove the agent, the LAN rules and the container together with uninstall --name <name> --yes |
| the router does not answer DNS from its uplink | a warning: /ip/dns allow-remote-requests is off, or a rule of raw prerouting or filter input drops a UDP query to port 53 that comes in on the interface of the active default route, walked as for the trap check, first match wins. A warning that the queries may pass when a rule matches on something doctor does not judge, a source address list say. IPv6 is not read | a drop rule for UDP and TCP port 53 on the uplink before any accept that takes it, or /ip/dns/set allow-remote-requests=no if no LAN host uses the router as its resolver |
| nothing tagged for <name> that these flags do not select | a warning: in every menu an install writes to, the objects that carry the install's tag are no more than the plan for these flags selects. An install made with other flags (--expose, other lists, another --subnet) leaves more | run status and uninstall with no shape flag, so that they read the install manifest, or with the flags that install was given |
Scroll sideways to see every column
- Storage. Under
install, the check uses the real tar’s size: twice the image, since the tar and the root extracted from it share the disk until the tar is deleted, plus 4 MiB.doctoron its own assumes a 7 MiB image. With--remote-imagenothing is uploaded, and the check asks for the extracted root plus 4 MiB. - Registry.
--remote-imagehands RouterOS the whole reference, registry host included, so the device-wide/container/does not decide where the pull goes, and mikroscope neither needs it set nor writes it.config registry-url doctorreads it only for the credential warning above: Troubleshooting. - Firewall. The interface-list check and the firewall check exist because of raw rules that drop every packet a container sends: Firewall lists.
Health checks
Section titled “Health checks”Standalone doctor, not the one inside install, then asks the agent that is already running what
its ring holds, in a health section after the checks. It reads the ring once, directly from this
host, at the address --subnet and --port give, the one status probes; pass --token (or
MIKROSCOPE_TOKEN) if the agent has one. It never goes through the relay or the --expose address,
and it never changes the exit status. It skips the section, saying why, in two cases:
health (what the running agent's ring shows now): skipped: no agent answered at 172.30.10.2:9123 from this host (…)when nothing answers /healthz within 3 s, and
skipped: the agent answered /healthz but its ring could not be read (…) when /healthz answers
but the ring does not, as with a token agent read without --token.
When it reads the ring, it prints how much it read and either a clean line or one WARN per
finding, each with its fix:, indented under health:
health (what the running agent's ring shows now): read 600 samples covering 60 s ok no loop signature, STP churn, link flap or softnet drop in the window WARN layer2-loop: 30 frames this router sent came back in on ether2 in the last 60 s, carrying the bridge's own address as their source fix: something downstream of ether2 reaches the router by a second path. …The ring is 60 s by default, so this sees what is happening now; the dashboards and the alert rules read history. It asks the agent for at most 10 000 samples, all within the same 3 s: the whole ring when it holds no more, otherwise the newest 10 000 (the last 1 000 s at 10 Hz, the last 100 s at 100 Hz). Each finding is a count of events a healthy router does not produce, not a threshold:
| Finding | Fires on |
|---|---|
layer2-loop |
three or more frames in the window that came back in on a port carrying the bridge’s own address as their source |
stp-churn |
a port that STP moved to learning at least three more times than it let it forward, without the loop signature |
link-flap |
two or more link-downs on one port anywhere in the samples read |
softnet-drops |
any packet the kernel dropped from its softnet backlog |
Scroll sideways to see every column
layer2-loop. A loop repeats the signature at the STP hello interval, 2 s by RouterOS default, thirty times in a 60 s ring, while RouterOS’s counters can show the port healthy and STP keeps it blocked (Layer-2 loop).stp-churn. Not a count of blocks: a healthy link-up logs three moves to learning at once and reaches forwarding seconds later, leaving learning minus forwarding at 0.link-flap. The collector’s ownlink-flapdetection counts differently: link-ups and link-downs together, two or more on one port within 60 s. A cable pulled and plugged back once is a flap there and not here.softnet-drops. A packet lost inside the router, where no interface counter sees it. Squeezes are not counted: a squeeze is the kernel pacing itself.
Each finding names the RouterOS port on a board the port map knows, and the kernel’s name otherwise. The kernel-log records are classified again from their text, so an older agent is read the same way. What has run against a live agent is on Tested on.
Your computer
Section titled “Your computer”| Need | For | Detail |
|---|---|---|
| ssh to the router | doctor, install, status, upgrade, uninstall |
an admin user, with a key or an ssh agent |
scp |
the tar and source routes | the image upload |
| an agent image | install, upgrade |
a registry pull, the release tar, or a build from a checkout with Go 1.27 |
| a route to the agent | record, forward, the probe |
a route to the /30 through the router, the API relay, or --expose |
| a RouterOS API user | the relay, --log-markers, the collector’s API tier |
read-only; it stays on your machine |
Scroll sideways to see every column
- ssh. The CLI runs the system
sshwithBatchMode=yesandConnectTimeout=15, so it cannot answer a password or a host-key prompt: use a key or an ssh agent, and for a new router add--ssh-option StrictHostKeyChecking=accept-new.--routertakesuser@hostor an ssh config alias;--ssh-portand--ssh-keyfall back to your ssh configuration when unset. - No ssh at all. The RouterOS script, the Script generator and the manual installs run on the router itself: Install methods.
- The agent. Network access has the three ways to reach it, and API user the user’s policy.
What has not been tested, such as a board of another architecture or a RouterOS before 7.24, is under Not tested.