mikrotik-routeros
Run MikroTik RouterOS CHR inside Docker using QEMU with multi-architecture support.
2.1K
A production-ready, QEMU-powered environment for running MikroTik RouterOS CHR inside Docker — on any architecture, without sacrificing your host OS.
This project packages MikroTik RouterOS (Cloud Hosted Router) inside a Docker container using QEMU for full x86_64 virtualization. It is designed for infrastructure engineers who need a reproducible, version-controlled MikroTik environment on any Linux server — with complete persistence, host file sharing, and safe port routing.
Because QEMU handles the x86_64 emulation layer, the Docker image itself is built for multiple CPU architectures. Whether your host is an AMD64 server, an ARM64 Raspberry Pi, or any other supported platform, you can pull and run the same image without any modifications.
| Feature | Description |
|---|---|
| Multi-Architecture | Pre-built for amd64, arm64, arm/v7, arm/v6, and 386 |
| Persistent Storage | Router configuration survives container rebuilds via a host-mounted virtual drive |
| Host ↔ Guest File Sharing | A local directory is exposed inside MikroTik's File Manager as a virtual FAT drive |
| Safe SSH Access | MikroTik SSH is remapped to port 2222 — your host SSH on port 22 is untouched |
| Dynamic Port Range | Ports 9000–9100 are pre-allocated for custom services — no Compose restarts needed |
| Version Pinning | Switch RouterOS versions via a single environment variable |
| KVM Acceleration | Hardware virtualization is enabled automatically when /dev/kvm is available, with graceful fallback to software emulation |
| Graceful Shutdown | SIGTERM is caught and forwarded to QEMU — MikroTik shuts down cleanly on docker compose down |
/dev/kvm available) for acceptable performanceNote: KVM is optional. If unavailable, QEMU falls back to software emulation automatically. Performance will be lower but the router will function correctly.
| Architecture | Tag |
|---|---|
| x86-64 | linux/amd64 |
| ARM 64-bit | linux/arm64 |
| ARM 32-bit v7 | linux/arm/v7 |
| ARM 32-bit v6 | linux/arm/v6 |
| x86 32-bit | linux/386 |
Docker will automatically pull the correct variant for your host platform.
git clone https://github.com/im-ecorp/mikrotik-routeros.git
cd mikrotik-routeros
Copy the example and set your desired RouterOS version:
cp .env.example .env
# Edit .env and set ROUTEROS_VERSION and optionally TZ
.env example:
ROUTEROS_VERSION=7.22.2
TZ=Asia/Tehran
If .env is omitted, the latest tag and Asia/Tehran timezone are used by default.
docker compose up -d
| Method | Address |
|---|---|
| Winbox | <server-ip>:8291 |
| SSH | ssh admin@<server-ip> -p 2222 |
| WebFig | http://<server-ip>:80 |
| API | <server-ip>:8728 |
Default credentials: admin / (no password — set one immediately)
┌──────────────────────────────────────────────────────┐
│ Docker Container (Alpine Linux) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ QEMU (x86_64 system emulation) │ │
│ │ │ │
│ │ MikroTik RouterOS CHR │ │
│ │ ├── chr.vdi (persistent disk) │ │
│ │ └── FAT drive (./shared) │ │
│ └────────────────┬───────────────────┘ │
│ │ TAP / bridge (qemubr0) │
└────────────────────┼─────────────────────────────────┘
│
Docker bridge network
172.24.0.0/16
Regardless of the host CPU architecture, QEMU always emulates an x86_64 machine for MikroTik — this is why the image runs identically on ARM and AMD64 hosts.
.
├── bin/
│ ├── entrypoint.sh # Container entrypoint & QEMU launcher
│ ├── generate-dhcpd-conf.py # Dynamic DHCP config generator
│ ├── qemu-ifup # TAP interface bring-up script
│ └── qemu-ifdown # TAP interface teardown script
├── data/ # Auto-created — stores chr.vdi (persistent)
├── shared/ # Auto-created — shared with MikroTik File Manager
├── .github/
│ └── workflows/
│ └── docker-image.yml # CI/CD pipeline for building & publishing images
├── Dockerfile
├── docker-compose.yml
└── .env.example
| Host Path | Container Path | Purpose |
|---|---|---|
./data | /routeros/data | Stores the persistent virtual hard drive (chr.vdi) |
./shared | /routeros/shared | Exposed to MikroTik as a virtual FAT drive |
Important: Never delete
./dataunless you intend to reset the router to factory defaults. Never rundocker compose down -v.
| Port | Protocol | Service |
|---|---|---|
21 | TCP | FTP |
22 → host 2222 | TCP | SSH (remapped) |
23 | TCP | Telnet |
80 | TCP | WebFig (HTTP) |
443 | TCP | WebFig (HTTPS) |
1194 | TCP/UDP | OpenVPN |
1701 | UDP | L2TP |
1723 | TCP | PPTP |
8291 | TCP | Winbox |
8728 | TCP | RouterOS API |
8729 | TCP | RouterOS API-SSL |
13231 | UDP | WireGuard |
9000–9100 | TCP | Reserved for custom services |
Tip: If you need to reassign a default MikroTik service port, use any port in the
9000–9100range — it will be immediately reachable without modifyingdocker-compose.yml.
Update ROUTEROS_VERSION in your .env file, then recreate the container:
docker compose down
docker compose up -d
The existing
chr.vdiin./datawill be reused. To start fresh with the new version's default config, delete./data/chr-*.vdibefore starting.
TZ=Europe/Berlin
docker compose down
Your configuration is safely stored in ./data and will persist for the next startup.
A GitHub Actions workflow is included at .github/workflows/docker-image.yml. It builds a multi-architecture image and pushes it to both Docker Hub and GitHub Container Registry (GHCR).
Configure these under Settings → Secrets and variables → Actions:
| Secret | Description |
|---|---|
DOCKERHUB_USERNAME | Your Docker Hub username |
DOCKERHUB_TOKEN | Docker Hub access token |
CR_PAT | GitHub Personal Access Token with write:packages scope |
| Input | Description |
|---|---|
version | RouterOS version to build (e.g. 7.22.2) |
tag_latest | Check to also push the latest tag for this version |
Each build produces the following tags (both with and without the v prefix for maximum compatibility):
hossein3piol/mikrotik-routeros:7.22.2
hossein3piol/mikrotik-routeros:v7.22.2
ghcr.io/im-ecorp/mikrotik-routeros:7.22.2
ghcr.io/im-ecorp/mikrotik-routeros:v7.22.2
# If "tag_latest" was checked:
hossein3piol/mikrotik-routeros:latest
ghcr.io/im-ecorp/mikrotik-routeros:latest
docker build \
--build-arg ROUTEROS_VERSION=7.22.2 \
-t hossein3piol/mikrotik-routeros:v7.22.2 \
-t hossein3piol/mikrotik-routeros:7.22.2 \
.
docker push hossein3piol/mikrotik-routeros:v7.22.2
docker push hossein3piol/mikrotik-routeros:7.22.2
Add a private IP range for VPN clients under IP → Pool.
For example: 172.24.0.0/16

Create or import the required certificates under System → Certificates.

Configure the PPP Profile under PPP → Profiles with the appropriate local and remote address settings.

Define user credentials under PPP → Secrets.

Create an OpenVPN Server interface under Interfaces → OpenVPN Server.
The example below uses port 4646 instead of the default 1194.

This step is critical for client internet access. Add a masquerade rule under IP → Firewall → NAT.

Container exits immediately
ls -la /dev/kvmdocker compose logs -fCannot connect via Winbox
docker compose ps8291 — wait until status shows healthyMikroTik lost its configuration after restart
./data directory exists and is writable by the containerdocker compose down -vSSH connection refused
2222, not 22ssh admin@<server-ip> -p 2222Bridge already exists error on restart
If this project saves you time in your infrastructure work, consider supporting its development:
TH1iDsFr2wjgpptghFBn6h7DVt88pp5WoHContent type
Image
Digest
sha256:ed2a53949…
Size
88.5 MB
Last updated
about 1 month ago
docker pull hossein3piol/mikrotik-routeros