Skip to content

Container

The RouterOS Container feature lets you run Linux containers directly on your MikroTik device alongside its normal networking functions. Containers are compatible with images from Docker Hub, GitHub Container Registry (GHCR), Quay, and other OCI-compliant registries.

:::danger Security Warning Containers require elevated system access and introduce significant security considerations:

  • Compromised routers can run malicious containers
  • Third-party images may contain vulnerabilities
  • Containers can potentially gain root access to RouterOS
  • Backdoors may persist even after container removal

Use only trusted images from verified publishers and isolate containers with firewall rules. :::

  • Pull and run standard Docker-compatible images directly from registries
  • Configure flexible container networking using virtual ethernet (veth) interfaces
  • Bind-mount router storage into containers for persistent data
  • Pass environment variables to configure containerized applications
  • Auto-restart and start-on-boot support for always-on services
ComponentPurpose
Container packageSeparate RouterOS package enabling container functionality
Device ModeSystem-level flag that must be enabled before containers can run
veth interfacesVirtual ethernet connecting containers to the router network
Container configGlobal registry URL, temp storage, and RAM limit settings
envsNamed lists of environment variables attached to containers
mountsPersistent host↔container directory bindings
RequirementDetails
Architecturearm, arm64, or x86
RouterOS versionv7.4 or later
StorageExternal disk (USB, SATA, NVMe) strongly recommended — internal flash will wear out quickly
RAM256 MB minimum; 512 MB+ recommended for most containers

Install the Container package from the MikroTik package repository for your device architecture before proceeding.

Containers require device mode to be enabled. Run the command below, then confirm with the reset button (or cold reboot on x86):

/system/device-mode/update container=yes

Verify it is active:

/system/device-mode/print

Set the registry URL and a temporary extraction directory on external storage:

/container/config/set registry-url=https://registry-1.docker.io tmpdir=disk1/tmp

For private registries or alternate sources (GHCR, Quay, LinuxServer):

/container/config/set registry-url=https://ghcr.io tmpdir=disk1/tmp

For authenticated registries (RouterOS v7.8+):

/container/config/set registry-url=https://registry-1.docker.io username=myuser password=mypassword
PropertyDescriptionDefault
registry-urlOCI registry base URLhttps://lscr.io/
tmpdirTemporary extraction directory
memory-highSoft RAM limit applied to all containersunlimited
usernameRegistry auth username (v7.8+)
passwordRegistry auth password (v7.8+)

Containers connect to the router network through veth (virtual ethernet) interfaces. Each container gets its own veth with an IP address and gateway.

Most use cases — containers share outbound internet access through NAT, and ports are forwarded inbound as needed:

# Create veth for the container
/interface/veth/add name=veth1 address=172.17.0.2/24 gateway=172.17.0.1
# Create a bridge to attach veth interfaces to
/interface/bridge/add name=containers
# Assign a host IP on the bridge (this is the container gateway)
/ip/address/add address=172.17.0.1/24 interface=containers
# Add veth to bridge
/interface/bridge/port/add bridge=containers interface=veth1
# NAT for outbound internet access
/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.17.0.0/24
# Example: forward port 80 to container
/ip/firewall/nat/add action=dstnat chain=dstnat dst-address=192.168.88.1 dst-port=80 protocol=tcp to-addresses=172.17.0.2 to-ports=80

Run separate container groups with no cross-traffic:

/interface/veth/add name=veth1 address=172.17.0.2/24 gateway=172.17.0.1
/interface/veth/add name=veth2 address=172.18.0.2/24 gateway=172.18.0.1
/interface/bridge/add name=containers1
/interface/bridge/add name=containers2
/ip/address/add address=172.17.0.1/24 interface=containers1
/ip/address/add address=172.18.0.1/24 interface=containers2
/interface/bridge/port/add bridge=containers1 interface=veth1
/interface/bridge/port/add bridge=containers2 interface=veth2
/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.17.0.0/24
/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.18.0.0/24

Attaches a container directly to an existing LAN bridge so it gets a LAN IP address:

/interface/veth/add name=veth1 address=192.168.88.2/24 gateway=192.168.88.1
/interface/bridge/port/add bridge=bridge interface=veth1

Layer 2 mode exposes all container ports to the LAN. Only use when broadcast-based service discovery is required (e.g. mDNS-dependent apps).

/ip/address/add address=172.17.0.1/24 interface=containers
/ipv6/address/add address=fd8d:5ad2:24:2::1 interface=containers
/interface/veth/add address=172.17.0.2/24,fd8d:5ad2:24:2::2/64 gateway=172.17.0.1 gateway6=fd8d:5ad2:24:2::1 name=veth1
/ip/firewall/nat/add action=masquerade chain=srcnat src-address=172.17.0.0/24
/ipv6/firewall/nat/add action=masquerade chain=srcnat src-address=fd8d:5ad2:24:2::/64

Environment variables configure containerized applications without modifying images. They are organized into named lists attached to containers at creation.

# Create a named env list with key/value pairs
/container/envs/add list=myapp-env key=TZ value=Europe/Riga
/container/envs/add list=myapp-env key=DB_HOST value=172.17.0.3
/container/envs/add list=myapp-env key=DB_PASSWORD value=secret
# View configured variables
/container/envs/print

Attach the list when creating the container using envlist=myapp-env.

Mounts bind a directory on the router’s filesystem into the container, so data survives container removal and updates.

# Create named mount points
/container/mounts/add name=myapp-data src=disk1/volumes/myapp dst=/data
/container/mounts/add name=myapp-config src=disk1/volumes/myapp-config dst=/config
# View configured mounts
/container/mounts/print

Attach mounts when creating the container using mounts=myapp-data,myapp-config.

/container/config/set registry-url=https://registry-1.docker.io tmpdir=disk1/tmp
/container/add remote-image=pihole/pihole:latest interface=veth1 root-dir=disk1/images/pihole name=pihole

Transfer a .tar image archive to the router via SFTP, then import:

/container/add file=disk1/myimage.tar interface=veth1 root-dir=disk1/images/myapp name=myapp
Terminal window
# On a workstation (match arch to your router)
podman pull --arch=arm64 docker.io/myimage:tag
podman save myimage > myimage.tar

Upload to router, then import with the file= method above.

PropertyDescription
remote-imageRegistry image name and tag to pull
filePath to local .tar image archive
interfaceveth interface for network connectivity
root-dirDirectory where container layers are extracted
envlistNamed environment variable list to inject
mountsComma-separated list of named mount points
start-on-bootAuto-start after reboot (yes/no)
loggingForward container stdout/stderr to RouterOS log
hostnameContainer hostname
dnsOverride DNS servers (comma-separated IPs)
userUID:GID for the container process (e.g. 0:0 for root)
cmdCommand to run (overrides image default)
entrypointEntrypoint executable
workdirWorking directory inside the container
auto-restart-intervalRestart delay on failure (e.g. 10s)
memory-highPer-container RAM hard limit
cpu-listCPU core affinity (e.g. 0,1)
devicesHost device passthrough (e.g. /dev/ttyUSB0)
/container/start pihole
/container/stop pihole
/container/restart pihole
/container/print detail
/container/shell pihole

With a specific user and command:

/container/shell myapp user=www-data cmd="php /var/www/html/cron.php" no-sh
/container/set pihole logging=yes start-on-boot=yes
/container/remove pihole

Pi-hole is a DNS-based ad blocker that runs well on most MikroTik hardware.

  • RouterOS v7.4+ with Container package installed
  • External USB/SATA/NVMe storage mounted as disk1
  • Device mode enabled
# 1. Enable device mode (requires reset button confirmation)
/system/device-mode/update container=yes
# 2. Configure registry
/container/config/set registry-url=https://registry-1.docker.io tmpdir=disk1/tmp
# 3. Create container network
/interface/veth/add name=veth1 address=172.17.0.2/24 gateway=172.17.0.1
/interface/bridge/add name=containers
/ip/address/add address=172.17.0.1/24 interface=containers
/interface/bridge/port/add bridge=containers interface=veth1
/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.17.0.0/24
/ip/firewall/nat/add action=dstnat chain=dstnat dst-address=192.168.88.1 dst-port=80 protocol=tcp to-addresses=172.17.0.2 to-ports=80
# 4. Environment variables
/container/envs/add list=ENV_PIHOLE key=TZ value="Europe/Riga"
/container/envs/add list=ENV_PIHOLE key=FTLCONF_webserver_api_password value="mysecurepassword"
/container/envs/add list=ENV_PIHOLE key=DNSMASQ_USER value="root"
# 5. Mounts for persistent data
/container/mounts/add name=MOUNT_PIHOLE src=disk1/volumes/pihole/pihole dst=/etc/pihole
/container/mounts/add name=MOUNT_PIHOLE_DNSMASQ src=disk1/volumes/pihole/dnsmasq.d dst=/etc/dnsmasq.d
# 6. Create and start container
/container/add remote-image=pihole/pihole interface=veth1 root-dir=disk1/images/pihole \
mounts=MOUNT_PIHOLE,MOUNT_PIHOLE_DNSMASQ envlist=ENV_PIHOLE \
name=pihole logging=yes start-on-boot=yes
/container/start pihole

Access the Pi-hole web interface at http://192.168.88.1/admin/.

  • Verify tmpdir is set to a path on external storage with sufficient free space
  • Check disk space: /system/resource/print
  • Review logs: /log print
  1. Confirm device mode: /system/device-mode/print
  2. Check veth interface exists: /interface/veth/print
  3. Verify mount source directories exist on storage
  4. Check for errors: /log print where topics~"container"
/container/print detail
  1. Confirm NAT rule exists: /ip/firewall/nat/print
  2. Check bridge has IP address: /ip/address/print
  3. Verify veth is in the bridge: /interface/bridge/port/print
  • Enable logging and check output: /container/set myapp logging=yes
  • Run an interactive shell to diagnose: /container/shell myapp
  • Verify image architecture matches your router (arm vs arm64 vs x86)

Limit container RAM to prevent router instability:

/container/config/set memory-high=256M

Or per-container:

/container/set pihole memory-high=128M