Skip to content

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

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

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).

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/package/enable container 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"
  1. Run, on the router’s terminal:

    /system/device-mode/update container=yes
  2. 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 5m00s

    CHR says update: turn off power in 5m to activate changes.

  3. 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/device-mode/print shows container: yes once it is done.

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.

The checks doctor runs
Check, as printedPasses whenThe 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 packagethe router's architecture-name is arm, arm64 or x86_64, the architectures MikroTik publishes a container package fornone: no agent can run on this router
the router picks the image's architecturewith --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 knownon arm, if the container stops with Exec format error, install from mikroscope-agent-armv5.tar with --agent-tar
architecture matches the --agent-tar imagewith --agent-tar: the tar's own architecture is the router's (amd64 for x86_64); --arch is not neededdownload the release asset it names, mikroscope-agent-<arch>.tar
architecture read from the routerwith neither image flag and --arch unset (or auto): always; install and upgrade build or load the image for the architecture doctor readnone
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 enableda container package exists with disabled=nodownload the container package for this architecture and RouterOS version, upload it and reboot; when it is there and disabled, /system/package/enable container and reboot
device-mode container=yes/system/device-mode reports container=yes/system/device-mode/update container=yes, 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 defaultfree memory on the router, or ask for less with --memory-max
free memory leaves room for the pulla 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 estimateinstall 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 MiBfree flash, or install with --disk tmpfs or --ephemeral where a tmpfs disk exists
disk <disk> existswith --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 checkfree space on that disk, or give a tmpfs disk a larger tmpfs-max-size
disk tmpfs is RAMwith --ephemeral: the disk in slot tmpfs is of type tmpfsfree the slot for a tmpfs disk, or install with --disk <slot> without --ephemeral
start-on-boot suits a root in RAMa 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 rootpass --start-on-boot no, or --ephemeral
veth name <veth> is free or oursno veth has that name, or the one that has it carries this install's tagpick another --veth (and --subnet), or remove the veth by hand if it is a leftover of yours
envlist <name>-env is free or oursno envlist has that name, or the one that has it holds this install's MIKROSCOPE_TAG entrypick another --name
install manifest <disk/>mikroscope/<name>.manifest.txt is free or oursno file is at the install manifest's path, or the one there holds this install's tag= linemove the file away, or pick another --name
container name <name> is free or ourswith --container-name: no container has that name, or the one that has it carries this install's tagpick another --container-name
subnet <subnet> does not overlap a routeno 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 outpick another /30 with --subnet
interface list <list> existsthe --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/list/add name=…, 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 questionnone
no firewall rule drops the agent's repliesdoctor 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 needsthe --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 lista 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 itfix 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'swith --expose: an interface of the router holds that addresspass the address the router has on its LAN, as /ip/address/print lists it
--lan-address is not on the uplinka 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 routepass 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 registrya 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 tokena 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 valueupgrade 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 uplinka 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 reada 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 selecta 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 morerun status and uninstall with no shape flag, so that they read the install manifest, or with the flags that install was given
  • 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. doctor on its own assumes a 7 MiB image. With --remote-image nothing is uploaded, and the check asks for the extracted root plus 4 MiB.
  • Registry. --remote-image hands RouterOS the whole reference, registry host included, so the device-wide /container/config registry-url does not decide where the pull goes, and mikroscope neither needs it set nor writes it. doctor reads 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.

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
  • 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 own link-flap detection 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.

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
  • ssh. The CLI runs the system ssh with BatchMode=yes and ConnectTimeout=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. --router takes user@host or an ssh config alias; --ssh-port and --ssh-key fall 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.