Install MikroTik Manager using Docker, Docker Compose, or as a MikroTik Container App.
docker pull ghcr.io/hreskiv/mikr:latest
This directory stores the SQLite database, TLS certificates, and backups. It persists across container restarts and updates.
mkdir -p /opt/mikr/data
docker run -d \
--name mikr-manager \
--restart unless-stopped \
-p 3000:3000 \
-p 3443:3443 \
-p 5514:5514/udp \
-p 5514:5514/tcp \
-v /opt/mikr/data:/app/data \
-e STORAGE_ADAPTER=sqlite \
ghcr.io/hreskiv/mikr:latest
Port 5514 is the optional syslog receiver (Log collector). Both UDP and TCP listeners run by default — RouterOS 7.x can send over either. Skip the -p 5514:5514/... lines if you don't need logs, or set SYSLOG_ENABLED=false / SYSLOG_TCP_ENABLED=false.
docker exec mikr-manager node scripts/seed.js
admin / admin. Change the password immediately after first login.
Navigate to http://your-server-ip:3000 and log in.
If you prefer Docker Compose, create these two files and run one command.
mkdir -p /opt/mikr && cd /opt/mikr
.env fileThis file stores your secrets. Generate random values and save them — you'll need the same keys on every update.
cat > /opt/mikr/.env <<EOF
JWT_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
.env immediately. If ENCRYPTION_KEY is lost, all stored device passwords become unreadable.
docker-compose.ymlcat > /opt/mikr/docker-compose.yml <<'EOF'
services:
mikr:
image: ghcr.io/hreskiv/mikr:latest
container_name: mikr-manager
restart: unless-stopped
ports:
- "3000:3000"
- "3443:3443"
- "5514:5514/udp" # optional — syslog receiver UDP
- "5514:5514/tcp" # optional — syslog receiver TCP
volumes:
- ./data:/app/data
environment:
- STORAGE_ADAPTER=sqlite
env_file:
- path: .env
required: false
EOF
cd /opt/mikr
docker compose up -d
docker exec mikr-manager node scripts/seed.js
admin / admin. Change the password immediately after first login.
cd /opt/mikr
docker compose pull
docker compose up -d
.env — your secrets persist across updates without extra flags.
Run mikr directly on a MikroTik router using the Container feature — no separate server needed.
Paste this into the YAML tab of the MikroTik Container App configuration:
name: mikr
descr: MikroTik Manager — web-based device management, monitoring, and configuration for MikroTik fleets.
page: https://mikr.app
category: networking
default-credentials: admin/admin
services:
mikr:
image: ghcr.io/hreskiv/mikr:latest
ports:
- "3000:3000:web"
- "3443:3443:web-ssl"
- "5514:5514/udp:syslog-udp"
- "5514:5514/tcp:syslog-tcp"
environment:
- HOST=0.0.0.0
- PORT=3000
- ENCRYPTION_KEY=<your-encryption-key>
- JWT_SECRET=<your-jwt-secret>
volumes:
- data:/app/data
restart: unless-stopped
openssl rand -hex 32 before pasting. The admin user is created automatically on first start — no seed command needed.
If you prefer configuring via CLI instead of the Container App UI:
# Enable container mode (requires reboot)
/system/device-mode/update container=yes
# Create veth interface for the container
/interface/veth/add name=veth-mikr address=172.17.0.2/24 gateway=172.17.0.1
# Create a bridge for containers
/interface/bridge/add name=bridge-containers
/interface/bridge/port/add bridge=bridge-containers interface=veth-mikr
/ip/address/add address=172.17.0.1/24 interface=bridge-containers
# NAT for container internet access
/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.17.0.0/24
# Port forwarding — access mikr from LAN
/ip/firewall/nat/add chain=dstnat action=dst-nat protocol=tcp \
dst-port=3000 to-addresses=172.17.0.2 to-ports=3000
# (optional) Syslog receiver — lets other MikroTiks send logs to mikr
/ip/firewall/nat/add chain=dstnat action=dst-nat protocol=udp \
dst-port=5514 to-addresses=172.17.0.2 to-ports=5514
# (optional) TCP syslog — useful when UDP is blocked in the path
/ip/firewall/nat/add chain=dstnat action=dst-nat protocol=tcp \
dst-port=5514 to-addresses=172.17.0.2 to-ports=5514
# Persistent data mount (use USB/NVMe, not NAND flash)
/container/mounts/add list=mikr-data src=disk1/mikr/data dst=/app/data
# Environment variables
/container/envs/add list=mikr-env key=HOST value=0.0.0.0
/container/envs/add list=mikr-env key=PORT value=3000
/container/envs/add list=mikr-env key=ENCRYPTION_KEY value=<your-key>
/container/envs/add list=mikr-env key=JWT_SECRET value=<your-secret>
# Pull and create container
/container/add remote-image=ghcr.io/hreskiv/mikr:latest \
interface=veth-mikr envlists=mikr-env mountlists=mikr-data \
hostname=mikr start-on-boot=yes logging=yes
# Start
/container/start 0
Pass variables with -e flags or mount an .env file. All are optional with sensible defaults.
.rsc mirror, etc. Anything you set as an env var here still wins (it shows as locked in the UI with a "from env" badge), so existing deployments keep working unchanged. Bootstrap-critical values (PORT, JWT_SECRET, ENCRYPTION_KEY, TLS_*, SYSLOG_PORT) stay env-only by design.
| Variable | Default | Description |
|---|---|---|
PORT | 3000 | HTTP server port |
HTTPS_PORT | 3443 | HTTPS server port |
JWT_SECRET | auto-generated | Secret for JWT tokens. If unset, a random value is generated on first start and persisted to data/.secrets.json. |
ENCRYPTION_KEY | auto-generated | 64-char hex key for AES-256-GCM device password encryption. Same auto-generate + persist behavior as JWT_SECRET. |
MONITOR_INTERVAL_MS | 60000 | Device polling interval in milliseconds |
MONITOR_CONCURRENCY | 10 | Max devices polled simultaneously |
TLS_ENABLED | false | Enable HTTPS (auto-generates self-signed cert) |
TLS_CERT_PATH | Path to custom TLS certificate | |
TLS_KEY_PATH | Path to custom TLS private key | |
METRICS_ENABLED | false | Enable the /metrics Prometheus endpoint. Disabled by default. |
METRICS_TOKEN | Bearer token for /metrics endpoint. Optional but recommended; empty = no auth. | |
SYSLOG_ENABLED | true | Enable the UDP syslog receiver (Log collector). |
SYSLOG_PORT | 5514 | UDP port for the syslog listener. |
SYSLOG_TCP_ENABLED | true | Enable the TCP syslog receiver (parallel to UDP, same port by default). Useful when UDP is blocked. |
SYSLOG_TCP_PORT | 5514 | TCP port for the syslog listener (defaults to SYSLOG_PORT). |
CVE_ENABLED | true | Enable RouterOS CVE alerting (daily NVD feed match, informational). |
NVD_API_KEY | — | Optional NVD API key — raises the fetch rate limit. Not required. |
CVE_CHECK_INTERVAL_MS | 86400000 | How often to refresh the NVD feed (default 24h). |
WEBAUTHN_RP_ID | — | Registrable domain for passkey / WebAuthn sign-in (e.g. mikr.example.com). Must be a real domain, not an IP address — the WebAuthn spec forbids IP RP IDs. Leave unset on LAN-IP / non-HTTPS installs; the Manager keeps working and the Settings page shows a clear "Passkey — Unavailable" notice. |
WEBAUTHN_ORIGINS | — | Comma-separated list of allowed origins for passkey ceremonies (e.g. https://mikr.example.com). Defaults to https://<WEBAUTHN_RP_ID> if unset but WEBAUTHN_RP_ID is set. |
LOG_LEVEL | info | Log level: debug, info, warn, error |
EXPORT_RSC_ENABLED | false | v1.30.0+. When true, mirror the latest .rsc script export of every device to /data/exports/<site>/<device>.rsc after each backup. One file per device, overwritten on each new backup. Mount /data/exports to a separate host volume for DR / git / rclone (see tip below). |
JWT_SECRET / ENCRYPTION_KEY set, the Manager generates strong random values and saves them to data/.secrets.json (mode 0600). Back up this file — losing it invalidates all sessions and makes stored device passwords unrecoverable. To use your own values, set them via env or .env (env always wins); generate with openssl rand -hex 32.
Example with custom secrets:
docker run -d \
--name mikr-manager \
--restart unless-stopped \
-p 3000:3000 \
-p 3443:3443 \
-v /opt/mikr/data:/app/data \
-e STORAGE_ADAPTER=sqlite \
-e JWT_SECRET=$(openssl rand -hex 32) \
-e ENCRYPTION_KEY=$(openssl rand -hex 32) \
ghcr.io/hreskiv/mikr:latest
WEBAUTHN_RP_ID and WEBAUTHN_ORIGINS are set and the Manager is served over HTTPS on the matching domain (e.g. via a Caddy / nginx reverse-proxy with Let's Encrypt). LAN-IP installs work fine without it — Settings just shows "Passkey — Unavailable" with an explanation. Changing WEBAUTHN_RP_ID later invalidates every previously registered passkey (a WebAuthn spec property).
ENCRYPTION_KEY after adding devices, existing stored passwords become unreadable. Save it securely.
.rsc mirror volume (v1.30.0+). If you enable the .rsc export feature, mount a separate host directory at /data/exports in your compose / docker run command — e.g. -v /opt/mikr-exports:/data/exports. Mikr will write one current .rsc per device under /data/exports/<site>/<device>.rsc, overwritten on each new backup. Point a git checkout at that path and commit after each write for a free change history, or rclone it to a NAS / cloud for 3-2-1 backups.
Your data is stored in /opt/mikr/data (mounted volume), so it survives container replacement.
cd /opt/mikr
docker compose pull
docker compose up -d
.env — your secrets persist across updates without extra flags.
# Pull the latest image
docker pull ghcr.io/hreskiv/mikr:latest
# Stop and remove the old container
docker stop mikr-manager
docker rm mikr-manager
# Start a new container with the same settings
docker run -d \
--name mikr-manager \
--restart unless-stopped \
-p 3000:3000 \
-p 3443:3443 \
-v /opt/mikr/data:/app/data \
--env-file /opt/mikr/.env \
ghcr.io/hreskiv/mikr:latest
-e flags instead of --env-file, include them again in the docker run command. See Environment Variables for the recommended .env file approach.
docker image prune -f
The Community edition supports up to 10 devices for free. To manage more devices, activate your license key in the app:
The license is stored in the database and persists across updates.
docker run -d \
--name mikr-manager \
--restart unless-stopped \
-p 3000:3000 \
-p 3443:3443 \
-v /opt/mikr/data:/app/data \
-e STORAGE_ADAPTER=sqlite \
-e TLS_ENABLED=true \
ghcr.io/hreskiv/mikr:latest
Access via https://your-server-ip:3443. Your browser will warn about the self-signed cert — this is expected.
docker run -d \
--name mikr-manager \
--restart unless-stopped \
-p 3000:3000 \
-p 3443:3443 \
-v /opt/mikr/data:/app/data \
-v /path/to/certs:/certs:ro \
-e STORAGE_ADAPTER=sqlite \
-e TLS_ENABLED=true \
-e TLS_CERT_PATH=/certs/fullchain.pem \
-e TLS_KEY_PATH=/certs/privkey.pem \
ghcr.io/hreskiv/mikr:latest
mikr exposes a /metrics endpoint in Prometheus text format. Connect it to Prometheus + Grafana for historical graphs of CPU, memory, temperature, and device availability.
The /metrics endpoint is disabled by default (returns 404). Opt in by setting METRICS_ENABLED=true:
# In .env file
METRICS_ENABLED=true
# Optional but recommended — Bearer token auth
METRICS_TOKEN=your-secret-token
Generate a strong random token:
openssl rand -hex 32
If METRICS_TOKEN is set, Prometheus must send a Bearer token or use ?token=your-secret-token.
curl http://your-server-ip:3000/metrics
Add this to your prometheus.yml:
scrape_configs:
- job_name: 'mikr'
scrape_interval: 60s
metrics_path: /metrics
static_configs:
- targets: ['your-server-ip:3000']
With token authentication:
scrape_configs:
- job_name: 'mikr'
scrape_interval: 60s
metrics_path: /metrics
authorization:
type: Bearer
credentials: 'your-secret-token'
static_configs:
- targets: ['your-server-ip:3000']
If mikr uses HTTPS (port 3443):
scrape_configs:
- job_name: 'mikr'
scheme: https
tls_config:
insecure_skip_verify: true
scrape_interval: 60s
metrics_path: /metrics
static_configs:
- targets: ['your-server-ip:3443']
| Metric | Description |
|---|---|
mikr_device_up | Device reachability (1 = online, 0 = offline) |
mikr_device_cpu_percent | CPU load percentage |
mikr_device_memory_percent | Memory usage percentage |
mikr_device_memory_free_bytes | Free memory in bytes |
mikr_device_memory_total_bytes | Total memory in bytes |
mikr_device_uptime_seconds | Device uptime in seconds |
mikr_device_temperature_celsius | Board temperature |
mikr_device_voltage_volts | Input voltage |
mikr_device_power_watts | Power consumption |
mikr_device_info | Device metadata (RouterOS version, model, architecture) |
mikr_devices_total | Total number of enabled devices |
mikr_devices_online | Number of online devices |
All per-device metrics include labels: name, host, site.
Import the ready-made dashboard template:
The dashboard includes: fleet overview stats, device status table, CPU/memory time series, temperature/voltage/power graphs, uptime tracking, RouterOS version distribution, and device model breakdown. Filter by site and device using the dropdown variables at the top.
scrape_interval to 60s to match — scraping more frequently won't provide additional data.
docker logs mikr-manager --tail 100
docker logs mikr-manager -f
Stop the container and re-run with -e LOG_LEVEL=debug to see SSH/REST connection details.
ss -tlnp | grep 3000ssh admin@device-ipssh, read, write, sensitive, api, rest-apiwww-ssl or www service is enabled on the MikroTikIf you lost access, re-run the seed script. It only creates a new admin if none exists. To force reset, access the SQLite database directly:
docker exec -it mikr-manager sh
node -e "
const {getStore}=require('./src/data/store');
const {hashPassword}=require('./src/services/auth.service');
(async()=>{
const store=getStore();
const hash=await hashPassword('newpassword');
store.db.prepare('UPDATE users SET password_hash=? WHERE username=?').run(hash,'admin');
console.log('Password reset to: newpassword');
})();"