Docker-контейнеры на RouterOS 7 — запуск сервисов на роутере
Начиная с RouterOS 7.4, MikroTik поддерживает запуск Docker-контейнеров непосредственно на маршрутизаторе. Это позволяет развернуть дополнительные сервисы — DNS-фильтрацию, мониторинг, reverse proxy — без отдельного сервера. В этом руководстве разберём требования, настройку контейнерного окружения, создание сетевых интерфейсов и запуск практических примеров.
Описание
Что такое контейнеры на RouterOS
Контейнеры на RouterOS — это полноценная поддержка OCI-совместимых (Docker) образов, интегрированная в систему. Маршрутизатор скачивает Docker-образ из Docker Hub или другого registry, создаёт контейнер и запускает его в изолированном окружении.
Требования
| Требование | Описание |
|---|---|
| RouterOS версия | 7.4 и выше (рекомендуется 7.12+ для стабильности) |
| Архитектура | ARM, ARM64 или x86. Образ контейнера должен соответствовать архитектуре маршрутизатора |
| Оперативная память | Минимум 256 МБ свободной RAM (рекомендуется 1 ГБ+) |
| Хранилище | Внешний USB-накопитель или встроенный NAND достаточного объёма |
| Устройства | RB5009, CCR2004, CCR2116, CHR или другие устройства с достаточными ресурсами |
Не рекомендуется запускать контейнеры на устройствах с 64-128 МБ RAM (hAP lite, hEX lite) — ресурсов недостаточно для стабильной работы.
Ограничения
- Нет docker-compose — каждый контейнер настраивается отдельно
- Нет GPU — невозможно использовать аппаратное ускорение
- Ограниченные ресурсы — маршрутизатор не сервер, тяжёлые приложения работать не будут
- Один registry за раз — по умолчанию Docker Hub, можно сменить
- Нет автообновления — обновление образа выполняется вручную
- Перезапуск — при перезагрузке маршрутизатора контейнеры запускаются заново (не возобновляются)
Настройка
Шаг 1: Включение режима контейнеров
Контейнеры отключены по умолчанию из соображений безопасности. Для включения необходимо активировать device-mode:
[admin@MikroTik] >/system/device-mode/update container=yes
После выполнения этой команды маршрутизатор потребует физическое подтверждение — нажатие кнопки reset на устройстве или перезагрузку. Это защита от удалённого включения контейнеров злоумышленником.
[admin@MikroTik] ># После нажатия reset или перезагрузки проверяем /system/device-mode/print # container: yes
Для CHR (Cloud Hosted Router) физическое подтверждение не требуется — контейнеры включаются сразу после перезагрузки.
Шаг 2: Настройка хранилища
Контейнеры и их данные занимают место. Рекомендуется использовать внешний USB-накопитель:
[admin@MikroTik] ># Проверяем доступные диски /disk print # Форматируем USB-накопитель (если нужно) /disk/format-drive usb1 file-system=ext4 label=containers # Указываем директорию для контейнеров /container/config set ram-high=512M tmpdir=usb1/tmp
Параметр ram-high ограничивает максимальное потребление RAM всеми контейнерами.
Если внешнего накопителя нет, контейнеры будут использовать встроенное хранилище. Убедитесь, что свободного места достаточно:
[admin@MikroTik] >/system/resource print # Смотрите поле free-hdd-space
Шаг 3: Настройка сети для контейнеров
Контейнеры подключаются к сети через виртуальные Ethernet-интерфейсы (veth). Каждый контейнер получает свой veth, который добавляется в bridge.
Создание veth-интерфейса:
[admin@MikroTik] >/interface/veth add name=veth-pihole \ address=172.17.0.2/24 \ gateway=172.17.0.1 \ comment="Pi-hole container"
Создание bridge для контейнеров:
[admin@MikroTik] >/interface/bridge add name=bridge-containers \ comment="Bridge for Docker containers" /ip/address add address=172.17.0.1/24 interface=bridge-containers
Добавление veth в bridge:
[admin@MikroTik] >/interface/bridge/port add bridge=bridge-containers interface=veth-pihole
NAT для выхода контейнеров в интернет:
[admin@MikroTik] >/ip/firewall/nat add chain=srcnat \ src-address=172.17.0.0/24 \ action=masquerade \ comment="NAT for containers"
Шаг 4: Настройка DNS для контейнеров
Контейнерам нужен DNS для загрузки обновлений и работы сервисов:
[admin@MikroTik] >/container/config set registry-url=https://registry-1.docker.io \ envdir=usb1/container-env \ ram-high=512M
Шаг 5: Настройка переменных окружения
Переменные окружения передаются контейнеру через файлы в специальной директории. Каждый envdir содержит файл env, где переменные записаны в формате KEY=VALUE.
[admin@MikroTik] ># Создание envdir для Pi-hole /container/envs add name=pihole-envs key=TZ value="Europe/Moscow" /container/envs add name=pihole-envs key=WEBPASSWORD value="PiholeAdmin2024" /container/envs add name=pihole-envs key=DNSMASQ_LISTENING value="all" /container/envs add name=pihole-envs key=PIHOLE_DNS_ value="8.8.8.8;1.1.1.1"
Пример 1: Pi-hole — блокировка рекламы
Pi-hole — DNS-сервер с фильтрацией рекламных и вредоносных доменов. Запуск на маршрутизаторе позволяет блокировать рекламу для всех устройств в сети без установки расширений в браузеры.
Создание veth (если не создан ранее):
[admin@MikroTik] >/interface/veth add name=veth-pihole \ address=172.17.0.2/24 \ gateway=172.17.0.1 /interface/bridge/port add bridge=bridge-containers interface=veth-pihole
Создание mount для сохранения данных:
[admin@MikroTik] >/container/mounts add name=pihole-data \ src=usb1/pihole/etc-pihole \ dst=/etc/pihole /container/mounts add name=pihole-dnsmasq \ src=usb1/pihole/etc-dnsmasq.d \ dst=/etc/dnsmasq.d
Загрузка и создание контейнера:
[admin@MikroTik] >/container add remote-image=pihole/pihole:latest \ interface=veth-pihole \ root-dir=usb1/pihole-root \ envlist=pihole-envs \ mounts=pihole-data,pihole-dnsmasq \ hostname=pihole \ start-on-boot=yes \ logging=yes \ comment="Pi-hole DNS ad blocker"
Загрузка образа займёт несколько минут в зависимости от скорости интернета. Следите за прогрессом:
[admin@MikroTik] >/container print # Статус изменится с "pulling" на "stopped", затем можно запустить
Запуск контейнера:
[admin@MikroTik] >/container start [find comment="Pi-hole DNS ad blocker"]
Перенаправление DNS-запросов на Pi-hole:
[admin@MikroTik] ># Все DNS-запросы из LAN перенаправляются на Pi-hole /ip/firewall/nat add chain=dstnat \ protocol=udp dst-port=53 \ src-address=192.168.1.0/24 \ action=dst-nat to-addresses=172.17.0.2 to-ports=53 \ comment="Redirect DNS to Pi-hole" /ip/firewall/nat add chain=dstnat \ protocol=tcp dst-port=53 \ src-address=192.168.1.0/24 \ action=dst-nat to-addresses=172.17.0.2 to-ports=53 \ comment="Redirect DNS TCP to Pi-hole"
Веб-интерфейс Pi-hole будет доступен по адресу http://172.17.0.2/admin. Для доступа из LAN добавьте DST-NAT или статический маршрут.
Пример 2: Nginx reverse proxy
Nginx может выступать как reverse proxy для доступа к внутренним сервисам через один внешний IP.
Создание veth:
[admin@MikroTik] >/interface/veth add name=veth-nginx \ address=172.17.0.3/24 \ gateway=172.17.0.1 /interface/bridge/port add bridge=bridge-containers interface=veth-nginx
Подготовка конфигурации Nginx:
Создайте файл конфигурации на USB-накопителе. Загрузите его через FTP/SFTP или Winbox (Files):
Файл usb1/nginx/conf.d/default.conf:
codeserver { listen 80; server_name service1.example.com; location / { proxy_pass http://192.168.1.100:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } server { listen 80; server_name service2.example.com; location / { proxy_pass http://192.168.1.101:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
Создание mount и контейнера:
[admin@MikroTik] >/container/mounts add name=nginx-conf \ src=usb1/nginx/conf.d \ dst=/etc/nginx/conf.d /container add remote-image=nginx:alpine \ interface=veth-nginx \ root-dir=usb1/nginx-root \ mounts=nginx-conf \ hostname=nginx \ start-on-boot=yes \ logging=yes \ comment="Nginx reverse proxy"
Port forwarding на Nginx:
[admin@MikroTik] >/ip/firewall/nat add chain=dstnat \ protocol=tcp dst-port=80 \ in-interface-list=WAN \ action=dst-nat to-addresses=172.17.0.3 to-ports=80 \ comment="Forward HTTP to Nginx" /ip/firewall/nat add chain=dstnat \ protocol=tcp dst-port=443 \ in-interface-list=WAN \ action=dst-nat to-addresses=172.17.0.3 to-ports=443 \ comment="Forward HTTPS to Nginx"
Управление контейнерами
Основные команды управления:
[admin@MikroTik] ># Список контейнеров /container print # Запуск контейнера /container start 0 # Остановка контейнера /container stop 0 # Просмотр логов контейнера /log print where topics~"container" # Удаление контейнера /container stop 0 /container remove 0 # Выполнение команды внутри контейнера (shell) /container/shell 0
Проверка
Проверка статуса контейнеров
[admin@MikroTik] >/container print
Возможные статусы:
| Статус | Описание |
|---|---|
| pulling | Загрузка образа из registry |
| extracting | Распаковка образа |
| stopped | Контейнер остановлен |
| running | Контейнер работает |
| error | Ошибка при запуске |
Проверка сетевой связности
[admin@MikroTik] ># Ping контейнера с маршрутизатора /ping 172.17.0.2 count=5 # Проверка порта /tool/fetch url="http://172.17.0.2/admin" mode=http dst-path=test.html /file/remove test.html
Проверка потребления ресурсов
[admin@MikroTik] ># Общая информация о ресурсах /system/resource print # Использование диска /disk print # Информация о контейнере /container print detail
Проверка логов контейнера
[admin@MikroTik] >/log print where topics~"container"
Для подробного логирования:
[admin@MikroTik] >/system/logging add topics=container action=memory
Проверка DNS-фильтрации Pi-hole
[admin@MikroTik] ># Запрос к Pi-hole напрямую /tool/dns-lookup name=ads.google.com server=172.17.0.2 # Если Pi-hole работает корректно, рекламные домены # вернут 0.0.0.0
С клиентского устройства в сети выполните запрос к рекламному домену — он должен быть заблокирован.
Типичные ошибки
1. Контейнер не запускается: «error — could not pull image»
Причина: нет доступа к Docker Hub, DNS не резолвит адрес registry, или архитектура образа не соответствует устройству.
Решение:
[admin@MikroTik] ># Проверьте DNS /ip/dns print /tool/dns-lookup name=registry-1.docker.io # Проверьте интернет-доступ /ping 8.8.8.8 count=3 # Проверьте архитектуру /system/resource print # Поле architecture-name покажет: arm, arm64, x86 и т.д. # Убедитесь, что образ поддерживает вашу архитектуру # Например, pihole/pihole:latest поддерживает arm64 и amd64
2. Контейнер запускается, но нет сети
Причина: veth не добавлен в bridge, нет IP-адреса или нет NAT.
Решение:
[admin@MikroTik] ># Проверьте veth /interface/veth print # Проверьте bridge port /interface/bridge/port print where interface~"veth" # Проверьте IP на bridge /ip/address print where interface=bridge-containers # Проверьте NAT /ip/firewall/nat print where comment~"container" # Проверьте, что bridge-containers имеет связность /ping 172.17.0.2 count=3
3. Ошибка «not enough disk space»
Причина: Docker-образы занимают от 50 МБ до нескольких ГБ. Встроенного хранилища может не хватить.
Решение:
[admin@MikroTik] ># Проверьте свободное место /system/resource print # Используйте внешний USB-накопитель /disk print # Выбирайте минимальные образы (alpine-варианты) # Вместо nginx:latest используйте nginx:alpine # Вместо pihole/pihole:latest можно использовать образы на Alpine
4. Контейнер «device-mode» не включается
Причина: не выполнено физическое подтверждение (нажатие reset).
Решение:
[admin@MikroTik] ># Проверьте текущий статус /system/device-mode/print # Если container=no, выполните /system/device-mode/update container=yes # Затем в течение нескольких минут нажмите кнопку reset на устройстве # ИЛИ перезагрузите маршрутизатор /system/reboot
На устройствах с кнопкой reset достаточно кратковременного нажатия (не удержания, чтобы не сбросить конфигурацию).
5. Данные контейнера теряются после перезапуска
Причина: не настроены mounts. Без них данные хранятся в overlay-файловой системе контейнера и удаляются при пересоздании.
Решение: всегда используйте mounts для persistent data:
[admin@MikroTik] ># Создайте mount для каждой директории с данными /container/mounts add name=app-data \ src=usb1/app/data \ dst=/app/data # Пересоздайте контейнер с mount /container stop 0 /container remove 0 /container add remote-image=image:tag \ interface=veth-app \ root-dir=usb1/app-root \ mounts=app-data \ start-on-boot=yes
6. Высокое потребление CPU/RAM контейнером
Причина: контейнерное приложение потребляет больше ресурсов, чем маршрутизатор может предоставить.
Решение:
[admin@MikroTik] ># Ограничьте RAM для контейнеров /container/config set ram-high=256M # Мониторьте ресурсы /system/resource print # Если CPU постоянно выше 80%, остановите контейнер /container stop 0
Рассмотрите использование более лёгких альтернатив:
- Вместо полноценного Pi-hole — AdGuard Home (меньше потребление)
- Вместо nginx — caddy (проще конфигурация, меньше памяти)
- Используйте образы на базе Alpine Linux
Рекомендации
- Используйте внешний USB для хранилища контейнеров — это продлит жизнь встроенного NAND
- Ограничивайте ram-high — контейнер не должен забирать RAM у основных функций маршрутизатора
- Выбирайте alpine-образы — они значительно меньше по размеру
- Настраивайте start-on-boot=yes для критических сервисов
- Регулярно обновляйте образы — удаляйте контейнер и создавайте заново с новым образом
- Мониторьте ресурсы — контейнеры не должны влиять на производительность маршрутизации
Контейнеры на RouterOS — мощная функция, которая превращает маршрутизатор в мини-сервер. Однако помните: основная задача маршрутизатора — маршрутизация. Не перегружайте его контейнерами. Для серьёзных сервисов используйте выделенный сервер, а на маршрутизаторе запускайте только лёгкие вспомогательные контейнеры.
/system/device-mode/update container=yes
# После нажатия reset или перезагрузки проверяем
/system/device-mode/print
# container: yes
# Проверяем доступные диски
/disk print
# Форматируем USB-накопитель (если нужно)
/disk/format-drive usb1 file-system=ext4 label=containers
# Указываем директорию для контейнеров
/container/config set ram-high=512M tmpdir=usb1/tmp
/system/resource print
# Смотрите поле free-hdd-space
/interface/veth add name=veth-pihole \
address=172.17.0.2/24 \
gateway=172.17.0.1 \
comment="Pi-hole container"
/interface/bridge add name=bridge-containers \
comment="Bridge for Docker containers"
/ip/address add address=172.17.0.1/24 interface=bridge-containers
/interface/bridge/port add bridge=bridge-containers interface=veth-pihole
/ip/firewall/nat add chain=srcnat \
src-address=172.17.0.0/24 \
action=masquerade \
comment="NAT for containers"
/container/config set registry-url=https://registry-1.docker.io \
envdir=usb1/container-env \
ram-high=512M
# Создание envdir для Pi-hole
/container/envs add name=pihole-envs key=TZ value="Europe/Moscow"
/container/envs add name=pihole-envs key=WEBPASSWORD value="PiholeAdmin2024"
/container/envs add name=pihole-envs key=DNSMASQ_LISTENING value="all"
/container/envs add name=pihole-envs key=PIHOLE_DNS_ value="8.8.8.8;1.1.1.1"
/interface/veth add name=veth-pihole \
address=172.17.0.2/24 \
gateway=172.17.0.1
/interface/bridge/port add bridge=bridge-containers interface=veth-pihole
/container/mounts add name=pihole-data \
src=usb1/pihole/etc-pihole \
dst=/etc/pihole
/container/mounts add name=pihole-dnsmasq \
src=usb1/pihole/etc-dnsmasq.d \
dst=/etc/dnsmasq.d
/container add remote-image=pihole/pihole:latest \
interface=veth-pihole \
root-dir=usb1/pihole-root \
envlist=pihole-envs \
mounts=pihole-data,pihole-dnsmasq \
hostname=pihole \
start-on-boot=yes \
logging=yes \
comment="Pi-hole DNS ad blocker"
/container print
# Статус изменится с "pulling" на "stopped", затем можно запустить
/container start [find comment="Pi-hole DNS ad blocker"]
# Все DNS-запросы из LAN перенаправляются на Pi-hole
/ip/firewall/nat add chain=dstnat \
protocol=udp dst-port=53 \
src-address=192.168.1.0/24 \
action=dst-nat to-addresses=172.17.0.2 to-ports=53 \
comment="Redirect DNS to Pi-hole"
/ip/firewall/nat add chain=dstnat \
protocol=tcp dst-port=53 \
src-address=192.168.1.0/24 \
action=dst-nat to-addresses=172.17.0.2 to-ports=53 \
comment="Redirect DNS TCP to Pi-hole"
/interface/veth add name=veth-nginx \
address=172.17.0.3/24 \
gateway=172.17.0.1
/interface/bridge/port add bridge=bridge-containers interface=veth-nginx
server {
listen 80;
server_name service1.example.com;
location / {
proxy_pass http://192.168.1.100:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 80;
server_name service2.example.com;
location / {
proxy_pass http://192.168.1.101:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
/container/mounts add name=nginx-conf \
src=usb1/nginx/conf.d \
dst=/etc/nginx/conf.d
/container add remote-image=nginx:alpine \
interface=veth-nginx \
root-dir=usb1/nginx-root \
mounts=nginx-conf \
hostname=nginx \
start-on-boot=yes \
logging=yes \
comment="Nginx reverse proxy"
/ip/firewall/nat add chain=dstnat \
protocol=tcp dst-port=80 \
in-interface-list=WAN \
action=dst-nat to-addresses=172.17.0.3 to-ports=80 \
comment="Forward HTTP to Nginx"
/ip/firewall/nat add chain=dstnat \
protocol=tcp dst-port=443 \
in-interface-list=WAN \
action=dst-nat to-addresses=172.17.0.3 to-ports=443 \
comment="Forward HTTPS to Nginx"
# Список контейнеров
/container print
# Запуск контейнера
/container start 0
# Остановка контейнера
/container stop 0
# Просмотр логов контейнера
/log print where topics~"container"
# Удаление контейнера
/container stop 0
/container remove 0
# Выполнение команды внутри контейнера (shell)
/container/shell 0
/container print
# Ping контейнера с маршрутизатора
/ping 172.17.0.2 count=5
# Проверка порта
/tool/fetch url="http://172.17.0.2/admin" mode=http dst-path=test.html
/file/remove test.html
# Общая информация о ресурсах
/system/resource print
# Использование диска
/disk print
# Информация о контейнере
/container print detail
/log print where topics~"container"
/system/logging add topics=container action=memory
# Запрос к Pi-hole напрямую
/tool/dns-lookup name=ads.google.com server=172.17.0.2
# Если Pi-hole работает корректно, рекламные домены
# вернут 0.0.0.0
# Проверьте DNS
/ip/dns print
/tool/dns-lookup name=registry-1.docker.io
# Проверьте интернет-доступ
/ping 8.8.8.8 count=3
# Проверьте архитектуру
/system/resource print
# Поле architecture-name покажет: arm, arm64, x86 и т.д.
# Убедитесь, что образ поддерживает вашу архитектуру
# Например, pihole/pihole:latest поддерживает arm64 и amd64
# Проверьте veth
/interface/veth print
# Проверьте bridge port
/interface/bridge/port print where interface~"veth"
# Проверьте IP на bridge
/ip/address print where interface=bridge-containers
# Проверьте NAT
/ip/firewall/nat print where comment~"container"
# Проверьте, что bridge-containers имеет связность
/ping 172.17.0.2 count=3
# Проверьте свободное место
/system/resource print
# Используйте внешний USB-накопитель
/disk print
# Выбирайте минимальные образы (alpine-варианты)
# Вместо nginx:latest используйте nginx:alpine
# Вместо pihole/pihole:latest можно использовать образы на Alpine
# Проверьте текущий статус
/system/device-mode/print
# Если container=no, выполните
/system/device-mode/update container=yes
# Затем в течение нескольких минут нажмите кнопку reset на устройстве
# ИЛИ перезагрузите маршрутизатор
/system/reboot
# Создайте mount для каждой директории с данными
/container/mounts add name=app-data \
src=usb1/app/data \
dst=/app/data
# Пересоздайте контейнер с mount
/container stop 0
/container remove 0
/container add remote-image=image:tag \
interface=veth-app \
root-dir=usb1/app-root \
mounts=app-data \
start-on-boot=yes
# Ограничьте RAM для контейнеров
/container/config set ram-high=256M
# Мониторьте ресурсы
/system/resource print
# Если CPU постоянно выше 80%, остановите контейнер
/container stop 0