This project provides a Docker image that runs a MikroTik RouterOS virtual machine inside QEMU.
It is designed to simulate a MikroTik RouterOS environment and is useful for development and testing, especially when working with the RouterOS API.
The image is well-suited for unit testing the routeros-api-php library in a controlled environment that closely mimics a real RouterOS setup.
For a production-ready RouterOS environment in Docker, consider the VR Network Lab project as an alternative.
The image is built for linux/amd64 and linux/arm64. On Apple Silicon (M1/M2/M3) Docker will pull the arm64 image automatically. RouterOS CHR is x86_64 only; on arm64 the image runs RouterOS inside QEMU emulation (no KVM on Mac/ARM), so it may be slower than on amd64 with KVM.
Pull and run the image (optionally pin to a RouterOS version tag):
docker pull evilfreelancer/docker-routeros
docker run -d -p 2222:22 -p 2223:23 -p 8728:8728 -p 8729:8729 -p 5900:5900 -ti evilfreelancer/docker-routerosPorts are exposed for SSH (22), telnet (23), API (8728, 8729), and VNC (5900). Wait 30-60 seconds after start for RouterOS to boot, then connect e.g. ssh -p 2222 admin@localhost or telnet localhost 2223. For inter-container networking and more reliable host access, use Docker Compose with two networks (see below).
Use two networks so that host port mapping (SSH, telnet, API) works reliably and the RouterOS guest is on a shared network with other containers. Copy docker-compose.dist.yml as a starting point.
services:
routeros:
image: evilfreelancer/docker-routeros:latest
restart: unless-stopped
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
- /dev/kvm # omit on Apple Silicon (no KVM there; container uses QEMU emulation)
ports:
- "2222:22"
- "2223:23"
- "8728:8728"
- "8729:8729"
volumes:
- ./routeros_data:/data
networks:
- default
- routeros_net
networks:
routeros_net:
driver: bridgeWith two networks the container gets eth0 (default) and eth1 (routeros_net). The entrypoint leaves eth0 for host port mapping (QEMU forwards ports into the guest) and puts eth1 in a bridge so the VM gets an IP on routeros_net and can reach other containers. After the container starts, wait 30-60 seconds for RouterOS to boot, then connect via SSH (port 2222) or telnet (port 2223).
You can easily create your own Dockerfile to include custom scripts or
configurations. The Docker image supports various tags, which are listed
here.
By default, the latest tag is used if no tag is specified.
FROM evilfreelancer/docker-routeros
ADD ["your-scripts.sh", "/"]
RUN /your-scripts.shTo build the image from source (e.g. for a specific RouterOS version):
git clone https://github.com/EvilFreelancer/docker-routeros.git
cd docker-routeros
docker build --build-arg ROUTEROS_VERSION=7.16 --tag ros .
docker run -d -p 2222:22 -p 8728:8728 -p 8729:8729 -p 5900:5900 -ti rosReplace 7.16 with the desired RouterOS version. After starting the container, wait 30-60 seconds for RouterOS to boot, then access via VNC (port 5900), SSH (port 2222), or telnet (port 2223).
To build for both amd64 and arm64 (e.g. for Apple Silicon and x86):
docker buildx create --use --name routeros_builder
docker buildx build --platform linux/amd64,linux/arm64 --build-arg ROUTEROS_VERSION=7.16 --tag ros .GitHub Actions runs on a schedule and on manual trigger. Only stable releases are considered (RC, alpha, beta are ignored). It:
- Reads the latest stable RouterOS version from the MikroTik download archive (e.g.
7.16.1). - Fetches all existing tags for this image from Docker Hub (with pagination).
- If that exact version is missing on Docker Hub: builds the image for
linux/amd64andlinux/arm64, then pushes tags:7.16.1(exact version)7.16(major.minor)7(major)latest
- If that version already exists: updates the moving tags so that
7.16,7, andlatestpoint to this image (no rebuild).
So latest and major / major.minor tags always follow the newest stable RouterOS.
-
Two networks (recommended): Attach the container to
defaultand a shared network (e.g.routeros_net). The container gets eth0 and eth1. The entrypoint leaves eth0 for host port mapping (QEMU user-mode networking with hostfwd), so SSH, telnet, API, Winbox from the host work. eth1 is put in a bridge with the VM TAP; the guest gets an IP on the shared network via DHCP and can reach other containers (and the internet via Docker gateway). Use this when you need both host access and inter-container visibility. -
Single network: If the container has only one interface (eth0), it is put in the bridge and the VM gets the container IP via DHCP. Host port mapping then relies on the bridge forwarding to the VM; inter-container works. For reliable host access (SSH/telnet without hangs), prefer two networks.
Two or more RouterOS on the same Docker network: each container needs a unique MAC. By default a unique MAC is generated per volume and stored in ROUTEROS_DATA_DIR/nic_mac; use different volumes per service so each gets its own MAC. To set a MAC explicitly, use ROUTEROS_NIC_MAC or write it to nic_mac in the data dir.
services:
routeros:
image: evilfreelancer/docker-routeros:latest
restart: unless-stopped
cap_add: [NET_ADMIN]
devices: ["/dev/net/tun", "/dev/kvm"]
ports: ["2222:22", "2223:23", "8728:8728", "8729:8729"]
volumes: ["./routeros_data:/data"]
networks: [default, routeros_net]
routeros2:
image: evilfreelancer/docker-routeros:latest
restart: unless-stopped
cap_add: [NET_ADMIN]
devices: ["/dev/net/tun", "/dev/kvm"]
ports: ["3222:22", "3223:23", "18728:8728", "18729:8729"]
volumes: ["./routeros_data2:/data"]
networks: [default, routeros_net]
# Each volume gets its own MAC in nic_mac; no need to set ROUTEROS_NIC_MAC unless you want a fixed value.
networks:
routeros_net:
driver: bridgeConnect from host: ssh -p 2222 admin@localhost or telnet localhost 2223. From inside either RouterOS guest you can reach the other by service name (e.g. routeros2) or by its IP on routeros_net.
services:
routeros:
image: evilfreelancer/docker-routeros:latest
restart: unless-stopped
cap_add: [NET_ADMIN]
devices: ["/dev/net/tun", "/dev/kvm"]
ports: ["2222:22", "2223:23", "8728:8728", "8729:8729"]
volumes: ["./routeros_data:/data"]
networks: [default, network1]
other-service:
image: your-image
networks: [network1]
networks:
network1:
driver: bridge| Variable | Default | Description |
|---|---|---|
ROUTEROS_NIC_MAC |
(generated) | MAC of the guest NIC on the bridge (TAP). At start the value is read from ROUTEROS_DATA_DIR/nic_mac if the file exists; otherwise the env is used if set, else a unique MAC is generated and written to that file so it persists per volume. Set this env only to override the stored or generated value. |
ROUTEROS_DHCP_DNS |
8.8.8.8 8.8.4.4 |
Space-separated DNS servers passed to the guest via DHCP. |
ROUTEROS_ETH_PROMISC |
1 |
Set to 1 to enable promiscuous mode on the bridge port (eth0 or eth1). Set to 0 to disable. |
ROUTEROS_DATA_DIR |
/data |
Directory used for persistent data: a second disk image file is stored here and the bridge NIC MAC is saved in nic_mac. Mount a Docker volume here so the second disk and MAC persist. See "Second disk (volume)" below. |
ROUTEROS_DISK2_SIZE |
256M |
Size of the second disk image when it is created automatically (e.g. 512M, 1G). Used only when ROUTEROS_DATA_DIR/disk2.raw does not exist yet. |
Example with custom DNS and MAC:
routeros:
image: evilfreelancer/docker-routeros:latest
environment:
ROUTEROS_NIC_MAC: "54:05:AB:CD:12:31"
ROUTEROS_DHCP_DNS: "1.1.1.1 1.0.0.1"
ROUTEROS_ETH_PROMISC: "1"When you mount a volume at ROUTEROS_DATA_DIR (default /data), the entrypoint creates a raw disk image file disk2.raw there if it does not exist (size from ROUTEROS_DISK2_SIZE, default 256M). This file is attached to the VM as a second IDE disk (hdb). RouterOS sees it as a block device (e.g. sata1 in /disk print).
One-time setup in RouterOS: format the second disk so it appears in /file:
/disk format sata1 file-system=ext4 label=data
After formatting, the disk is mounted automatically and appears in /file print (e.g. as sata1). You can use it for backups, scripts, logs, proxy cache, or any persistent data. The data is stored in disk2.raw on the host, so it survives container restarts and image updates.
The same directory holds nic_mac for the bridge NIC; each volume keeps its own MAC and its own second disk.
Example:
routeros-persistent:
image: evilfreelancer/docker-routeros:latest
restart: unless-stopped
cap_add: [NET_ADMIN]
devices: ["/dev/net/tun", "/dev/kvm"]
ports: ["32222:22", "32223:23", "38728:8728", "38729:8729"]
volumes:
- ./routeros_data:/data
environment:
ROUTEROS_DISK2_SIZE: "512M" # optional; default 256M
networks: [default, routeros_net]Here ./routeros_data is mounted at /data. On first start, routeros_data/disk2.raw is created (512M in this example). In RouterOS, run /disk format sata1 file-system=ext4 label=data once; then the second disk is available in /file and persists across restarts.
The table below summarizes the ports exposed by the Docker image, catering to various services and protocols used by RouterOS.
| Description | Ports |
|---|---|
| Defaults | 21, 22, 23, 80, 443, 8291, 8728, 8729 |
| IPSec | 50, 51, 500/udp, 4500/udp |
| OpenVPN | 1194/tcp, 1194/udp |
| L2TP | 1701 |
| PPTP | 1723 |
- MikroTik RouterOS in Docker using QEMU (Habr) - Setup guide for RouterOS in Docker with QEMU.
- RouterOS API Client - PHP library for the RouterOS API.
- VR Network Lab - Run network equipment in Docker; alternative for production-like RouterOS.
- qemu-docker - QEMU in Docker.
- QEMU/KVM on Docker - QEMU/KVM virtualization in Docker.
- tenable/routeros - RouterOS security research tooling and proof of concepts (Winbox, JSProxy, scanners, honeypots).
- https://help.mikrotik.com/docs/spaces/ROS/pages/91193346/Disks