Guide · MikroTik

How to run a MikroTik NetFlow collector with Docker Compose and keep it deployed

A MikroTik router will tell you exactly which device is using the connection, if something collects the flow records it exports. The open-source mikrotik-home-netflow-plus collector does that in one container: live throughput, per-device history, a flow map, alerts and a wall display. This guide configures the router, runs the collector with plain Compose, then turns it into a Simple Docker Ops application with an encrypted secret, digest-pinned releases, a health gate, upgrades and rollback.

Updated 2026-09-3012 minute readApplies to RouterOS 7, collector 1.1.0, Docker Engine 24 and newer

What you get

RouterOS 7 can export a record of every connection the router forwards (Traffic Flow, as NetFlow or IPFIX) and expose its live state over a read-only API. mikrotik-home-netflow-plus, Apache 2.0 licensed, combines the two: one Go binary, one container, one SQLite file, and a web interface with a live throughput chart, a page per device, a Sankey flow map, nine alert rules with a webhook, and a full-screen wall display. The published image is built for amd64 and arm64, so a Raspberry Pi 4 or 5 next to the router is as good a host as an x86 server.

Before you start

  • A MikroTik router on RouterOS 7 and a terminal on it: WinBox › New Terminal, WebFig › Terminal, or SSH.
  • A Linux host on the router's LAN with Docker Engine 24 or newer and the Compose v2 plugin, running as a systemd service. Raspberry Pi OS, Debian, Ubuntu or Armbian on a Pi 4 or 5, a mini PC or a VM.
  • A static address or a static DHCP lease for that host. The router will export to it and restrict the API user to it.
  • For step 4, a free Simple Docker Ops account.

When you are done

  • The collector running as a release: image pinned to its digest, health-checked, restarted by the agent if it fails.
  • The router password encrypted in the portal, delivered to the agent only with a release, never in a file you manage on the host.
  • Upgrades and rollback from the portal or the API, without SSH or an inbound port.
  • Optionally, the wall display on a Raspberry Pi screen.
LaneSourceDelayGives you
Accounting laneIPFIX flow records the router pushes over UDP 205515 to 75 secondsHistory, totals, rankings, the flow map, alerts. Every flow the router forwarded, FastTracked ones included.
Live lane (optional)The RouterOS API, polled with a read-only user over TLS on port 87291 to 2 secondsCurrent rates, the active connection table, device names from DHCP, destination names from the router's DNS cache.

Step 1Configure the router

Two lines turn on flow export. The read-only API user and its certificate are optional but worth the extra minute: without them the collector still keeps history and raises alerts, but live views show a 90-second average and devices are named by MAC address. Replace <COLLECTOR_IP> with the host's address and <ROUTER_LAN_IP> with the router's LAN address.

  1. Send flow records to the host

    active-flow-timeout=1m is the lowest value RouterOS accepts and the one the collector expects; the default of 30 minutes would hide a long download for half an hour. FastTrack can stay on.

    RouterOS terminal
    /ip traffic-flow set enabled=yes active-flow-timeout=1m
    /ip traffic-flow target add dst-address=<COLLECTOR_IP> port=2055 version=ipfix \
        src-address=<ROUTER_LAN_IP> v9-template-refresh=20 v9-template-timeout=1m
  2. Add a read-only API user

    The flowmon group can read and use the API, nothing else, and the account only works from the host's address. The collector never needs an administrative account.

    RouterOS terminal
    /user group add name=flowmon policy=read,api
    /user add name=flowmon group=flowmon password=<STRONG_PASSWORD> address=<COLLECTOR_IP>/32
  3. Give the API a certificate

    The encrypted API service (api-ssl, port 8729) has no certificate out of the box. A self-signed one is enough; the collector pins it on first use. Wait for the sign command to finish before the last line.

    RouterOS terminal
    /certificate add name=api-cert common-name=router.lan key-size=2048 days-valid=3650 \
        key-usage=key-cert-sign,crl-sign,digital-signature,key-encipherment,tls-server
    /certificate sign api-cert
    /ip service set api-ssl certificate=api-cert

    /ip traffic-flow target print shows one target pointing at the host, and /ip service print where name=api-ssl shows api-cert.

The full router guide

The project's router setup guide explains every setting, the IPFIX fields, running without TLS on port 8728, keeping the router clock right, and how to undo it all. Once the collector runs, its Status page shows this whole script with your addresses filled in.

Step 2Prepare the host

  1. Check Docker

    The collector needs Docker and Compose v2; the Simple Docker Ops agent additionally needs Docker running as the systemd service docker.service. Docker from snap, rootless Docker and Podman do not qualify; install Docker Engine from Docker's own repository (the supported devices page has the commands).

    shell
    docker version --format '{{.Server.Version}}'   # 24 or newer
    docker compose version --short                  # 2 or newer (Docker's repository ships 5.x; the major is what counts)
    systemctl is-active docker                      # active
  2. Check the ports are free

    The collector runs with host networking and binds UDP 2055 and TCP 8080 directly. If something already holds 8080, pick another port now and use it in step 3 and step 4.

    shell
    sudo ss -lntup | grep -E ':(8080|2055)\b'       # no output means both are free
  3. Pin the host's address

    A static address, or a static lease on the router (/ip dhcp-server lease make-static). The router's export target and the flowmon user's address restriction both point at it.

Step 3Run it with plain Compose

This is the repository's own quick start and it works on any Docker host. Do it once even if you plan to use Simple Docker Ops: it proves the router side before the platform is involved.

  1. Clone and configure

    shell
    git clone https://github.com/thedyerman/mikrotik-home-netflow-plus.git
    cd mikrotik-home-netflow-plus
    cp deploy/env.example deploy/.env && chmod 600 deploy/.env

    Edit deploy/.env. The five lines that matter:

    deploy/.env
    NFP_EXPORTERS=192.168.88.1        # the router's LAN address: packets from anywhere else are dropped
    NFP_ROUTER_ADDR=192.168.88.1      # same address; leave empty to run from flow records only
    NFP_ROUTER_USER=flowmon
    NFP_ROUTER_PASSWORD=<STRONG_PASSWORD>
    NFP_ROUTER_TLS=true

    The compose file it belongs to is short. Host networking, a named volume for the database, and the environment from the file:

    deploy/docker-compose.yml
    services:
      netflow:
        image: kcdyer/mikrotik-home-netflow-plus:1.1.0
        container_name: mikrotik-home-netflow-plus
        restart: unless-stopped
        network_mode: host
        env_file: .env
        volumes:
          - netflow-data:/data
    
    volumes:
      netflow-data:
  2. Start it

    shell
    docker compose -f deploy/docker-compose.yml up -d
  3. Check the Status page

    Open http://<host>:8080 and click Status. The indicator at the bottom of the sidebar says Live when both lanes work and Delayed when only flow records arrive.

CardWhat you want to see
Flow export"Last export just now", records being decoded, none lost
Router API"Connected", with the router's model and RouterOS version
Flow coverageAbove 95% after about five minutes of traffic
NetworkYour LAN under local networks and the right interface under WAN interfaces

A working collector. Stop here if one machine, updated by hand, is all you need. Keep going to hand the lifecycle to Simple Docker Ops.

Step 4Make it a Simple Docker Ops application

The difference from step 3 is where the configuration lives. There is no .env file on the host: every ${NAME} in the compose text becomes a variable of the application, the password becomes a secret, and the agent writes what the container needs on the device at deploy time. The image is public, so no registry credentials are involved.

  1. Pair the host

    On the host, run the installer. It checks Docker, installs the agent as a systemd service and prints Pair this device: with a code such as X72K-4PQ9. The code is single-use and valid for 15 minutes.

    shell, on the host
    curl -fsSL https://simpledockerops.com/install | sudo sh

    In the portal click + Add device, enter the code under Enrollment code, keep the host name or give it one, leave Fleet at No fleet for a single server, and click Enroll device. The agent connects outbound only; nothing new listens on the host.

    The device appears on the Devices page as online, with its architecture and Docker version.

  2. Stop the plain Compose stack

    Both would bind the same ports. Run this on the host before the first deployment. The release below starts with a fresh database; the collector fills it again within minutes.

    shell, on the host
    docker compose -f deploy/docker-compose.yml down
  3. Create the application

    Applications+ New application. Name netflow, paste the text below into docker-compose.yml, and Create. It is the repository's deploy/simpledockerops-compose.yml: the same container, with every setting as a variable instead of a file.

    docker-compose.yml, pasted into the application
    services:
      netflow:
        image: kcdyer/mikrotik-home-netflow-plus:1.1.0
        restart: unless-stopped
        network_mode: host
        environment:
          NFP_EXPORTERS: ${ROUTER_IP}
          NFP_ROUTER_ADDR: ${ROUTER_IP}
          NFP_ROUTER_USER: ${ROUTER_USER}
          NFP_ROUTER_PASSWORD: ${ROUTER_PASSWORD}
          NFP_ROUTER_TLS: "true"
          NFP_SITES: ${SITES}
          NFP_HTTP_LISTEN: ":${HTTP_PORT}"
        volumes:
          - netflow-data:/data
    
    volumes:
      netflow-data:
  4. Fill in the variables

    The portal detects the ${…} names and lists them in the application's Variables card with a Default, a Required flag and a Secret flag. ROUTER_PASSWORD is marked secret automatically because of its name; its default is disabled and says "set under Secrets". Fill in the others and Save working copy.

  5. Store the router password as a secret

    SecretsSet a secret: Scope Application, Which netflow, Name ROUTER_PASSWORD, Value the flowmon password, Save secret. It is encrypted at rest, shown as dots from now on, and delivered to the agent only when a release needs it.

  6. Cut a release

    Back on the application page, Cut release. Version 1.1.0, Notes as you like. The portal resolves the image to its digest and records the platforms it provides; the preflight notes would warn if there were no linux/arm64 build.

    The release shows as ready with linux/amd64 and linux/arm64 and the frozen compose text with the image pinned by @sha256:.

  7. Deploy it

    ReleasesDeploy on the release. Target One device, choose the host, Start rollout. (The device page has the same thing as Choose a release to deploy… and Deploy to this device.) The agent pulls the image, starts the container and reports healthy once the container is running and its built-in health check has passed for 30 seconds.

    The deployment shows the device as healthy. http://<host>:8080 opens the collector and its Status page reads as in step 3.

VariableValueSecret
ROUTER_IPThe router's LAN address, for example 192.168.88.1no
ROUTER_USERflowmonno
ROUTER_PASSWORDSet under Secrets, never as a defaultyes
SITESOptional names for remote sites, for example office=192.168.99.0/24. Leave empty if you have noneno
HTTP_PORT8080, or another free portno
Several sites, one application

The application's defaults are frozen into each release. Fleet variables in the fleet editor and Device variables on the device page override them, and saving either re-pushes the current release straight away. That is how one application serves several routers: the same release everywhere, a different ROUTER_IP per device, and a ROUTER_PASSWORD secret scoped to each device.

Running from flow records only

No API user on the router? Remove the three NFP_ROUTER_* lines from the compose text and skip ROUTER_USER and ROUTER_PASSWORD. Keep NFP_EXPORTERS: it is what makes the collector accept the router's packets.

Upgrades and rollback

  1. Upgrade

    In the application, change the image tag in the compose text to the new version, Save working copy, Cut release with that version, deploy it. The netflow-data volume is kept, and open browser tabs and wall displays notice the new version and reload by themselves.

  2. Roll back

    On the deployment page, Roll back returns every device the deployment touched to the release it was running before, in one wave. Deploying any earlier release again does the same. In a fleet rollout, On failure set to Roll back affected devices makes a release that never turns healthy roll back by itself.

Keep the same application

The compose project on the device is named after the application's slug (sdko-netflow), and so is the volume (sdko-netflow_netflow-data). Renaming the application keeps its slug. Deleting it and creating a new one gives a new slug, a new volume and an empty database. Upgrade by cutting new releases of the same application.

Your own build

The published image covers normal use. To run a modified version, build it and push it somewhere the device can pull from. The managed registry gives every organisation private repositories under its own slug at registry.simpledockerops.com (one gigabyte on the Free plan). Devices pull from it with a credential of their own, so there is no docker login to run on the host.

shell, on your workstation, in the repository
docker login registry.simpledockerops.com     # portal account, a registry password, or an API key
docker buildx build --platform linux/amd64,linux/arm64 --build-arg VERSION=1.1.0 \
  -t registry.simpledockerops.com/<your-org>/mikrotik-home-netflow-plus:1.1.0 --push .

Then put that image name in the compose text and cut a release. Docker Hub, GHCR and any other OCI registry work too: add the credential under RegistryExternal registries and it is used to resolve the digest at release time and for the device's pull. Build for the architecture of the host, or for both as above.

Automate with the API

Everything above can be scripted with an API key from DevelopersAPI keys. The key starts with sdko_, is shown once, and acts with admin rights, so keep it in your CI system's secret store.

shell
# Cut a release, then deploy it to one device
curl -X POST https://app.simpledockerops.com/api/v1/applications/APP_ID/releases \
  -H "Authorization: Bearer $SDKO_KEY" -H "Content-Type: application/json" \
  -d '{"version":"1.1.0","notes":"first release"}'

curl -X POST https://app.simpledockerops.com/api/v1/deployments \
  -H "Authorization: Bearer $SDKO_KEY" -H "Content-Type: application/json" \
  -d '{"release_id":"RELEASE_ID","target_type":"device","target_id":"DEVICE_ID",
       "strategy":{"waves":[100],"min_healthy_s":30,"on_failure":"rollback"}}'

# Watch it, or roll it back
curl https://app.simpledockerops.com/api/v1/deployments/DEPLOYMENT_ID -H "Authorization: Bearer $SDKO_KEY"
curl -X POST https://app.simpledockerops.com/api/v1/deployments/DEPLOYMENT_ID/rollback -H "Authorization: Bearer $SDKO_KEY"

waves are cumulative percentages of the target; for a single device they are always [100]. min_healthy_s is how long the container must stay healthy before the device counts, and on_failure is pause or rollback. The API reference has the full shape.

Put it on a wall display

The collector's /dashboard page is a full-screen wall display built for a small always-on screen: download and upload right now, a live chart, top devices and destinations, today's totals, and a banner when the router stops exporting or an alert fires. The sister guide on the DisplayOps site, Live MikroTik traffic monitor on a wall display, puts it on a Raspberry Pi screen: which URL to use, the password prompt, and keeping the Pi on it. It starts from the collector this guide leaves you with.

Troubleshooting

  • The container keeps restarting. Capture its logs from the device page (Logs, then Capture) or with docker logs on the host. bind: address already in use means another program holds the port: set HTTP_PORT to a free one and redeploy.
  • The deployment never turns healthy. Same cause, or the image has no build for the host's architecture. The deploy dialog blocks with "Architecture validation failed" when the release lacks the device's platform; the published image has both, a build of your own may not.
  • Status says "No flow records received yet". The router's target does not point at this host, or UDP 2055 is blocked on the way. /ip traffic-flow target print on the router.
  • Packets "rejected from other sources". The router exports from an address other than ROUTER_IP. Set src-address on the target to the router's LAN address.
  • Router API "invalid user name or password". The secret is wrong, or the flowmon user's address restriction does not match the host. Set the secret again and deploy the release again; devices keep the old value until their next deployment.
  • Router API "TLS handshake failed". api-ssl has no certificate. Step 1.3.
  • A changed variable has no effect. Application defaults are frozen into each release: cut a new one. Fleet and device variables apply as soon as they are saved.
  • History is gone after a redeploy. The application was deleted and recreated, which gave the volume a new name. See the callout above.
  • The installer refuses to run. Docker from snap, rootless Docker or Podman. Install Docker Engine from Docker's repository; the installer prints the steps and the supported devices page lists them.

Security checklist

  • The router account has the read and api policies only and works only from the host's address.
  • api-ssl has a certificate and the collector pins it on first use. Without TLS the password crosses the LAN in the clear.
  • ROUTER_PASSWORD is a secret, never a default: encrypted at rest, never shown again, delivered to the agent only with a release.
  • If anyone other than you can reach the host, add NFP_AUTH_PASSWORD: ${WEB_PASSWORD} to the compose text and set it as another secret. Put a reverse proxy with TLS in front if the interface leaves the LAN.
  • The agent opens no inbound ports, and the collector's two ports are on your LAN only. Nothing in this guide opens anything to the internet.
  • The API key has admin rights. Keep it in a secret store and create a separate key per system that uses it.

MikroTik NetFlow collector: questions we get

Do I need Simple Docker Ops to run the collector?

No. The repository's compose file and a .env file run it on any Linux machine with Docker, which is what step 3 does. Simple Docker Ops takes over what happens after the first "up": the router password as an encrypted secret instead of a file on the host, releases pinned to image digests, a health gate on every deployment, and upgrades and rollback from the portal or the API without SSH.

Why host networking?

The router sends flow records to the host's address on UDP 2055, and the collector only accepts packets from the exporters it knows. With host networking the packets arrive with the router's real source address and the web interface listens on HTTP_PORT directly, so there is no port mapping to maintain and nothing to get wrong about UDP through Docker's NAT. The agent runs the compose file as written; it does not rewrite or restrict it.

Can one application serve several routers?

Yes. The application's defaults are frozen into each release, and fleet variables and device variables override them: the same release, a different ROUTER_IP per device, and a ROUTER_PASSWORD secret scoped to each device. Saving fleet or device variables re-pushes the current release straight away.

What happens if the host is offline during a deployment?

The release waits for it. When the host reconnects the agent reconciles, reports health, and the deployment accounts for it. The agent treats each release as a transaction and reconciles again after a power loss, so nothing is half-applied.

What does it cost?

The collector is one device. The Free plan covers five devices and one gigabyte of managed registry, with no card and no time limit. Fleets, staged rollouts and encrypted secrets are included on every plan.

Where does the traffic data go?

Nowhere. Everything stays in one SQLite file in the netflow-data volume on the host. The collector makes no outbound connections except to the router and to a webhook you configure yourself. Simple Docker Ops sees the container's health, its captured logs and its configuration, never the traffic.

Which versions does this apply to?

RouterOS 7 (the collector was developed against 7.20), mikrotik-home-netflow-plus 1.1.0, Docker Engine 24 or newer and Compose v2 or newer. The collector is Apache 2.0 licensed and independent of both MikroTik and Simple Docker Ops.

Deploy the collector once and forget about SSH.

Five devices and one gigabyte of registry on the Free plan. Paste the compose file, cut a release, deploy.

curl -fsSL https://simpledockerops.com/install | sudo sh