Диагноз: что идёт не так и почему ты здесь
Поднял container на MikroTik. Вроде запустил NPM. Открываешь порт 81 — не отвечает. Или отвечает, но SSL не выдаёт. Или всё работало, потом роутер перезагрузился и контейнеры не поднялись. Знакомо?
Проблема почти всегда в одном из трёх мест: неправильный сетевой интерфейс для контейнеров, не настроен veth или bridge, либо контейнеры не знают друг о друге по имени хоста. Let’s Encrypt падает отдельно — там свои причины.
Эта статья закрывает весь путь: от нуля до рабочего HTTPS с автопродлением сертификатов и публикацией внутренних сервисов наружу.
Что получишь на выходе: рабочий MikroTik с container package, два запущенных контейнера (MariaDB + Nginx Proxy Manager), SSL через Let’s Encrypt, reverse proxy для внутренних сервисов, автозапуск после перезагрузки.
Время: 1-2 часа при первой настройке. Повторно — 20 минут.
Что нужно:
- MikroTik с RouterOS 7.4 или новее (лучше 7.6+)
- Минимум 256 MB RAM свободной, рекомендую 512 MB
- USB-накопитель или дополнительный диск — встроенной памяти роутера не хватит
- Белый IP или доменное имя с A-записью на твой IP (для Let’s Encrypt)
- Открытые порты 80 и 443 наружу
В статье:
- Включение и настройка container package
- Создание сети для контейнеров
- Запуск MariaDB
- Запуск Nginx Proxy Manager
- Настройка firewall и NAT
- Первый proxy host с SSL
- Автозапуск
- Troubleshooting
Системные требования и совместимость версий
| Компонент | Минимум | Рекомендуется | Примечание |
|---|---|---|---|
| RouterOS | 7.4 | 7.10+ | Container package появился в 7.4, стабилизировался в 7.6 |
| Архитектура | x86 / arm64 | arm64 или x86 | arm32 (hEX, RB4xx) — не поддерживается |
| RAM | 256 MB свободной | 512 MB+ | MariaDB любит память |
| Хранилище | 2 GB | 8 GB USB/SSD | Образы занимают ~500 MB каждый |
| Nginx Proxy Manager | 2.10.x | последняя | jc21/nginx-proxy-manager |
| MariaDB | 10.6 | 10.11 LTS | mariadb:10.11 |
На момент публикации актуальна RouterOS 7.15. Перед установкой проверь свежие релизы на mikrotik.com/download.
Архитектура: как это работает
Прежде чем запускать команды — пойми схему. Без этого потом не разберёшься что сломалось и где.
Вот что происходит по шагам:
- Интернет стучится на 80 и 443 твоего роутера
- MikroTik через DST-NAT перебрасывает трафик на виртуальный интерфейс veth
- NPM принимает запросы, смотрит на hostname и проксирует запрос к внутреннему сервису
- MariaDB живёт в той же виртуальной сети — NPM обращается к ней по имени контейнера
- Порт 81 — веб-интерфейс NPM, только для внутреннего управления
Таблица портов
| Порт | Протокол | Контейнер | Назначение | Открыть наружу? |
|---|---|---|---|---|
| 80 | TCP | NPM | HTTP, редирект на HTTPS + Let’s Encrypt challenge | Да |
| 443 | TCP | NPM | HTTPS reverse proxy | Да |
| 81 | TCP | NPM | Веб-интерфейс управления | Нет, только LAN |
| 3306 | TCP | MariaDB | База данных NPM | Никогда |
Шаг 1: Включаем container package
Container — это отдельный пакет в RouterOS. По умолчанию не установлен. Сначала проверь что у тебя есть и что нужно скачать.
Подключись к роутеру через Winbox или SSH и проверь архитектуру:
/system resource print
Смотри строку architecture-name. Запомни — пригодится при скачивании пакета. Типичные значения: arm64, x86, arm. На arm (32-bit) контейнеры не работают — это RB750, hEX и им подобные.
Скачай extra packages для своей версии RouterOS с mikrotik.com/download. Нужен файл вида container-7.x.x-arm64.npk. Загрузи его через Winbox (Files) или по SCP:
scp container-7.15.0-arm64.npk admin@192.168.88.1:/
После загрузки перезагрузи роутер:
/system reboot
После перезагрузки проверь что пакет установился:
/system package print
В списке должен появиться container со статусом installed.
Теперь включи функциональность контейнеров — по умолчанию она выключена даже с установленным пакетом:
/system/device-mode/update container=yes
/system/device-mode/print
Убедись что container: yes в выводе.
Шаг 2: Готовим хранилище
Встроенная память роутера — не для контейнеров. Образы весят сотни мегабайт, данные MariaDB растут. Тебе нужен USB-накопитель или дополнительный диск.
Подключи USB. Проверь что он определился:
/disk print
Должен появиться диск, обычно с именем usb1 или disk1. Если диск не отформатирован — отформатируй:
/disk format-drive disk1 file-system=ext4 label=containers
Создай структуру директорий для контейнеров:
/file make-dir disk1/npm
/file make-dir disk1/npm/data
/file make-dir disk1/npm/letsencrypt
/file make-dir disk1/mariadb
/file make-dir disk1/mariadb/data
/file make-dir disk1/images
Путь к диску в RouterOS выглядит как disk1/ — без ведущего слеша. Запомни это, будешь использовать при создании контейнеров.
Шаг 3: Настраиваем сеть для контейнеров
Это самое важное место. Большинство проблем с NPM на MikroTik — именно тут. Контейнерам нужна своя изолированная сеть, и они должны видеть друг друга по IP.
Создаём виртуальный bridge для контейнеров:
/interface bridge add name=containers-bridge comment="Bridge for docker containers"
Назначаем IP этому bridge — это будет шлюз для контейнеров:
/ip address add address=172.18.0.1/24 interface=containers-bridge comment="Container network gateway"
Создаём veth-интерфейс для MariaDB:
/interface veth add name=veth-mariadb address=172.18.0.3/24 gateway=172.18.0.1
Создаём veth-интерфейс для NPM:
/interface veth add name=veth-npm address=172.18.0.2/24 gateway=172.18.0.1
Добавляем оба veth в bridge:
/interface bridge port add bridge=containers-bridge interface=veth-mariadb
/interface bridge port add bridge=containers-bridge interface=veth-npm
Проверяем:
/interface bridge port print
Оба интерфейса должны быть в списке со статусом active.
Теперь разреши контейнерам ходить в интернет — нужен masquerade:
/ip firewall nat add chain=srcnat src-address=172.18.0.0/24 action=masquerade comment="Container network NAT"
Шаг 4: Настраиваем реестр образов
По умолчанию MikroTik тянет образы с Docker Hub. Укажи это явно:
/container config set registry-url=https://registry-1.docker.io tmpdir=disk1/images
Параметр tmpdir — куда скачиваются образы во время pull. Без этого образ попытается распаковаться во встроенную память и не влезет.
Шаг 5: Запускаем MariaDB
NPM без базы данных не запустится. Поэтому сначала — MariaDB.
Создаём контейнер MariaDB. Обрати внимание: environment переменные — это пароли и имя базы, их NPM будет использовать при подключении:
/container add \
image=mariadb:10.11 \
interface=veth-mariadb \
root-dir=disk1/mariadb \
mounts=mariadb-data \
envs=mariadb-envs \
hostname=mariadb \
start-on-boot=yes \
logging=yes \
comment="MariaDB for NPM"
Сначала создай переменные окружения — они нужны до создания контейнера:
/container envs add name=mariadb-envs key=MYSQL_ROOT_PASSWORD value=RootPass123!
/container envs add name=mariadb-envs key=MYSQL_DATABASE value=npm
/container envs add name=mariadb-envs key=MYSQL_USER value=npm
/container envs add name=mariadb-envs key=MYSQL_PASSWORD value=NpmDbPass456!
Создай mount для данных MariaDB:
/container mounts add name=mariadb-data src=disk1/mariadb/data dst=/var/lib/mysql
Теперь создаём сам контейнер:
/container add \
image=mariadb:10.11 \
interface=veth-mariadb \
root-dir=disk1/mariadb \
mounts=mariadb-data \
envs=mariadb-envs \
hostname=mariadb \
start-on-boot=yes \
logging=yes \
comment="MariaDB for NPM"
Запускаем pull образа:
/container pull [find comment="MariaDB for NPM"]
Это займёт несколько минут — образ весит около 400 MB. Следи за прогрессом:
/container print
Пока статус extracting — ждёшь. Когда станет stopped — образ готов, запускай:
/container start [find comment="MariaDB for NPM"]
Подожди 20-30 секунд. MariaDB при первом запуске инициализирует базу — это дольше обычного старта.
/container print detail
Статус должен стать running.
Шаг 6: Запускаем Nginx Proxy Manager
Создаём переменные окружения для NPM — он должен знать как подключиться к MariaDB. IP 172.18.0.3 — это адрес veth-mariadb, который мы назначили раньше:
/container envs add name=npm-envs key=DB_MYSQL_HOST value=172.18.0.3
/container envs add name=npm-envs key=DB_MYSQL_PORT value=3306
/container envs add name=npm-envs key=DB_MYSQL_USER value=npm
/container envs add name=npm-envs key=DB_MYSQL_PASSWORD value=NpmDbPass456!
/container envs add name=npm-envs key=DB_MYSQL_NAME value=npm
Создаём mount-точки для NPM:
/container mounts add name=npm-data src=disk1/npm/data dst=/data
/container mounts add name=npm-letsencrypt src=disk1/npm/letsencrypt dst=/etc/letsencrypt
Создаём контейнер NPM:
/container add \
image=jc21/nginx-proxy-manager:latest \
interface=veth-npm \
root-dir=disk1/npm \
mounts=npm-data,npm-letsencrypt \
envs=npm-envs \
hostname=npm \
start-on-boot=yes \
logging=yes \
comment="Nginx Proxy Manager"
Pull образа:
/container pull [find comment="Nginx Proxy Manager"]
NPM весит около 500 MB — снова ждём. После завершения запускаем:
/container start [find comment="Nginx Proxy Manager"]
NPM при первом запуске создаёт схему базы данных — занимает 30-60 секунд. Если он не стартует с первого раза — подожди пока MariaDB полностью инициализируется и попробуй ещё раз.
Шаг 7: Firewall и NAT для публикации портов
Контейнеры запущены, но снаружи к ним не добраться — нужно прокинуть порты через NAT и открыть firewall.
DST-NAT для HTTP (порт 80) — пробрасываем на NPM:
/ip firewall nat add \
chain=dstnat \
in-interface-list=WAN \
protocol=tcp \
dst-port=80 \
action=dst-nat \
to-addresses=172.18.0.2 \
to-ports=80 \
comment="NPM HTTP"
DST-NAT для HTTPS (порт 443):
/ip firewall nat add \
chain=dstnat \
in-interface-list=WAN \
protocol=tcp \
dst-port=443 \
action=dst-nat \
to-addresses=172.18.0.2 \
to-ports=443 \
comment="NPM HTTPS"
Открываем firewall для этих портов:
/ip firewall filter add \
chain=forward \
in-interface-list=WAN \
protocol=tcp \
dst-port=80,443 \
dst-address=172.18.0.2 \
action=accept \
comment="Allow NPM from WAN" \
place-before=0
Для доступа к панели управления NPM изнутри сети — порт 81 на LAN:
/ip firewall nat add \
chain=dstnat \
in-interface-list=LAN \
protocol=tcp \
dst-port=81 \
action=dst-nat \
to-addresses=172.18.0.2 \
to-ports=81 \
comment="NPM Admin Panel"
Проверяем весь NAT:
/ip firewall nat print where comment~"NPM"
Шаг 8: Первый вход в Nginx Proxy Manager
Открой браузер и перейди на http://192.168.88.1:81 — это внутренний IP твоего MikroTik.
Дефолтные учётные данные при первом входе:
- Email:
admin@example.com - Password:
changeme
NPM сразу попросит сменить email и пароль — сделай это. Не откладывай.
Шаг 9: Настройка первого proxy host с SSL
Перед этим убедись что:
- Домен направлен A-записью на твой внешний IP
- Порт 80 доступен снаружи — Let’s Encrypt делает HTTP challenge через него
- Внутренний сервис поднят и доступен по IP из сети роутера
В интерфейсе NPM: Hosts -> Proxy Hosts -> Add Proxy Host.
Заполняй так:
- Domain Names: твой домен, например
app.example.com - Scheme:
http(если внутренний сервис без SSL) - Forward Hostname / IP: IP внутреннего сервиса, например
192.168.88.100 - Forward Port: порт сервиса, например
8080 - Websockets Support: включи если нужно
- Block Common Exploits: включи
Переходи на вкладку SSL:
- SSL Certificate: Request a new SSL Certificate
- Force SSL: включи
- HTTP/2 Support: включи
- HSTS Enabled: включи если уверен
- Email Address: твой email для Let’s Encrypt
- I Agree to Terms: отметь
Нажми Save. NPM уйдёт на несколько секунд — пройдёт ACME challenge и получит сертификат. Если всё сделано правильно — статус станет зелёным.
Шаг 10: Проверка работоспособности
Проверяем что контейнеры живы:
/container print
Оба должны быть в статусе running.
Смотрим логи NPM:
/container log print [find comment="Nginx Proxy Manager"]
Смотрим логи MariaDB:
/container log print [find comment="MariaDB for NPM"]
Проверяем доступность портов изнутри роутера:
/tool tcp-ping 172.18.0.2 port=80
/tool tcp-ping 172.18.0.2 port=443
/tool tcp-ping 172.18.0.2 port=81
/tool tcp-ping 172.18.0.3 port=3306
Проверка сети между контейнерами:
/ping 172.18.0.2 count=3
/ping 172.18.0.3 count=3
Тест внешней доступности — с другого устройства или через онлайн-сервис:
curl -I https://app.example.com
Должен вернуть HTTP/2 200 или редирект от внутреннего сервиса.
Безопасность
Не пропускай этот раздел. Роутер с открытым 80/443 — это цель.
Закрой порт 81 (панель NPM) от интернета — оставь только для LAN:
/ip firewall filter add \
chain=forward \
in-interface-list=WAN \
protocol=tcp \
dst-port=81 \
action=drop \
comment="Block NPM admin from WAN" \
place-before=0
Закрой MariaDB от всех кроме контейнерной сети:
/ip firewall filter add \
chain=forward \
protocol=tcp \
dst-port=3306 \
src-address=!172.18.0.0/24 \
action=drop \
comment="Block MariaDB from outside container net"
Ограничь количество соединений на 80/443 — базовая защита от DDoS:
/ip firewall filter add \
chain=forward \
in-interface-list=WAN \
protocol=tcp \
dst-port=80,443 \
connection-limit=50,32 \
action=drop \
comment="Limit connections per IP to NPM"
Включи fail2ban или rate limiting в самом NPM — там есть встроенные настройки в Advanced разделе каждого хоста.
Убедись что SSH на роутер не доступен с WAN без явного правила, или ограничь по IP:
/ip firewall filter add \
chain=input \
in-interface-list=WAN \
protocol=tcp \
dst-port=22 \
src-address-list=allowed-ssh \
action=accept \
comment="SSH from trusted IPs only"
Резервное копирование
Что бэкапить:
- Директория
disk1/npm/data— конфигурация NPM, все proxy hosts - Директория
disk1/npm/letsencrypt— SSL сертификаты и ключи - Директория
disk1/mariadb/data— данные базы - RouterOS конфиг:
/exportили бэкап через Winbox
Как часто: еженедельно для конфигов, перед каждым обновлением NPM или RouterOS.
Скрипт бэкапа конфига RouterOS:
/system backup save name=("mikrotik-backup-" . [:tostr [/system clock get date]])
/export file=("mikrotik-config-" . [:tostr [/system clock get date]])
Для бэкапа данных контейнеров — останови их, скопируй директории на внешнее хранилище, запусти обратно. Горячий бэкап MariaDB без остановки — через mysqldump внутри контейнера, но это отдельная история.
Как восстановить: развернуть структуру директорий, скопировать бэкап данных, создать контейнеры заново по той же схеме. Сертификаты Let’s Encrypt восстановятся из letsencrypt директории.
Автообновление NPM
Обновление NPM — это pull нового образа и пересоздание контейнера. Данные сохранятся в mount-точках.
Останови NPM:
/container stop [find comment="Nginx Proxy Manager"]
Удали старый контейнер (данные в mount-точках сохранятся):
/container remove [find comment="Nginx Proxy Manager"]
Создай контейнер заново с теми же параметрами (из шага 6) — при pull подтянется новый образ. Запусти:
/container start [find comment="Nginx Proxy Manager"]
Troubleshooting: что ломается и как чинить
Контейнер не стартует — статус stopped сразу после запуска
Причина: ошибка в конфигурации — неверные env переменные, недоступный mount, нехватка памяти.
Решение: смотри логи сразу после попытки запуска.
/container log print [find comment="Nginx Proxy Manager"]
Частая причина для NPM — MariaDB ещё не готова. Подожди 30 секунд после старта MariaDB, потом запускай NPM.
NPM запустился, но порт 81 не отвечает
Причина: проблема с veth или NAT правилом.
Решение: проверь что veth-npm в bridge и имеет правильный IP.
/interface veth print
/interface bridge port print
/ip address print where interface=containers-bridge
Проверь NAT правило для порта 81:
/ip firewall nat print where dst-port=81
Let’s Encrypt не выдаёт сертификат — ошибка «Invalid response from domain»
Причина: порт 80 снаружи недоступен или A-запись домена не указывает на твой IP.
Решение: проверь что port 80 NAT-правило активно и firewall его пропускает. Проверь A-запись:
/tool dns-lookup app.example.com
Проверь порт 80 снаружи через онлайн-сервис типа portchecker.co. Let’s Encrypt должен добраться до http://app.example.com/.well-known/acme-challenge/.
Контейнеры не видят интернет — pull образа зависает
Причина: нет masquerade для контейнерной сети или неверный gateway в veth.
Решение: проверь NAT масквераде и маршрутизацию.
/ip firewall nat print where comment~"Container"
/ip route print
Убедись что gateway=172.18.0.1 в обоих veth-интерфейсах и что 172.18.0.1 назначен на containers-bridge.
После перезагрузки контейнеры не запускаются
Причина: start-on-boot не установлен или USB-диск не успел подключиться до старта контейнеров.
Решение: проверь параметр start-on-boot.
/container print detail
Если start-on-boot=no — исправь:
/container set [find comment="MariaDB for NPM"] start-on-boot=yes
/container set [find comment="Nginx Proxy Manager"] start-on-boot=yes
Если USB не успевает монтироваться — добавь задержку через scheduler. Инженер с опытом скажет: задержка в 15 секунд решает 90% проблем с USB на RouterOS.
/system scheduler add \
name=start-containers \
on-event="/container start [find start-on-boot=yes]" \
start-time=startup \
interval=0 \
comment="Start containers after boot"
NPM не подключается к MariaDB — ошибка «Access denied for user»
Причина: пароли в env переменных NPM и MariaDB не совпадают.
Решение: проверь что MYSQL_PASSWORD в MariaDB совпадает с DB_MYSQL_PASSWORD в NPM. Оба должны быть одинаковыми. Если менял — пересоздай контейнеры.
/container envs print where name=mariadb-envs
/container envs print where name=npm-envs
Proxy host показывает 502 Bad Gateway
Причина: NPM не может добраться до внутреннего сервиса по указанному IP и порту.
Решение: проверь что внутренний сервис поднят и доступен с роутера.
/tool tcp-ping 192.168.88.100 port=8080
Убедись что firewall не блокирует трафик из контейнерной сети к LAN. NPM ходит к внутренним сервисам с адреса 172.18.0.2:
/ip firewall filter print where chain=forward
Если есть правило drop для трафика между VLAN — добавь исключение для контейнерной сети.
Образ не скачивается — ошибка «no space left»
Причина: tmpdir не указан или указан на встроенную память.
Решение:
/container config print
Убедись что tmpdir=disk1/images. Если нет — установи и попробуй pull снова.
/container config set tmpdir=disk1/images
Альтернативные решения
NPM — не единственный вариант. Смотри что ещё есть:
Traefik — более гибкий, поддерживает автодискавери Docker. Сложнее в настройке, требует больше ресурсов. Оправдан если планируешь много контейнеров с автоматическим роутингом.
Caddy — минималистичный, автоматически управляет SSL. Конфиг в одном файле. Минус — нет веб-интерфейса, всё через текстовый конфиг. Потребляет меньше памяти чем NPM.
HAProxy — если нужен L4/L7 балансировщик с серьёзной нагрузкой. На роутере избыточен если ты не обрабатываешь тысячи соединений в секунду.
Нативный MikroTik proxy — /ip proxy. Очень ограниченный, без SSL termination. Не замена NPM.
Выбрал NPM потому что: веб-интерфейс, простая настройка Let’s Encrypt, активное сообщество, хорошо документированные Docker-образы. Для домашней лабы или небольшого офиса — оптимальный выбор.
Профилактика: как не сломать снова
Контейнер который работает — это ещё не конец истории. Вот что делать чтобы оно работало долго.
Мониторинг: добавь проверку состояния контейнеров через Netwatch или скрипт с уведомлением. Минимум — проверяй раз в час что оба контейнера в статусе running.
/system scheduler add \
name=check-containers \
on-event=":foreach c in=[/container find status!=running] do={/log warning (\"Container down: \" . [/container get \$c comment])}" \
start-time=00:00:00 \
interval=1h \
comment="Check containers health"
Перед обновлением RouterOS: останови контейнеры, сделай бэкап, обнови, проверь что container package совместим с новой версией OS, запусти.
Следи за диском: MariaDB растёт. Раз в месяц смотри свободное место.
/disk print
Ротация логов: в NPM зайди в Settings и настрой log rotation чтобы логи не съели весь диск.
Проверяй сертификаты: Let’s Encrypt выдаёт на 90 дней, NPM обновляет автоматически за 30 дней до истечения. Но только если порт 80 открыт. После любого изменения firewall — проверяй что 80 работает снаружи.
FAQ
Почему NPM не запускается если MariaDB ещё не готова?
NPM при старте сразу пытается подключиться к базе данных. Если MariaDB ещё инициализируется — NPM получает ошибку подключения и падает. Решение: запускай MariaDB первой, жди 30-60 секунд и только потом запускай NPM. В production это решается через healthcheck — NPM ждёт пока база не ответит на тестовый запрос. В RouterOS container такого механизма нет, поэтому порядок запуска и scheduler с задержкой — твои друзья.
Как проверить что Let’s Encrypt сертификат получен и когда он истекает?
В веб-интерфейсе NPM: SSL Certificates -> список сертификатов с датами истечения. Через командную строку роутера:
/file print where name~"letsencrypt"
Или подключись к NPM и смотри файлы в /etc/letsencrypt/live/yourdomain.com/. Дату истечения покажет:
openssl x509 -noout -enddate -in cert.pem
Что если роутер не поддерживает arm64 и container package не ставится?
Проверь архитектуру командой /system resource print. Если видишь arm (32-bit) — официально контейнеры не поддерживаются. Варианты: поставить отдельный одноплатник (Raspberry Pi 4 с arm64) рядом с роутером и запустить NPM там, а с MikroTik делать только NAT и роутинг. Это даже лучше для производительности — роутер занимается роутингом, не крутит контейнеры.
Как подключить несколько доменов к одному внутреннему сервису?
В настройках proxy host в поле Domain Names добавь несколько доменов через Enter. NPM создаст один сертификат на все домены через SAN (Subject Alternative Names). Все домены должны иметь A-запись на твой IP.
Можно ли запустить NPM без MariaDB — только на SQLite?
Да. Убери все DB_MYSQL_* переменные из env NPM — тогда он автоматически использует встроенный SQLite. Плюс: проще, меньше ресурсов, нет зависимости от MariaDB. Минус: SQLite медленнее при большом количестве операций, не рекомендован для production с высокой нагрузкой. Для домашней лабы — вполне.
Почему после перезагрузки роутера сертификаты пропадают?
Они не пропадают — mount-точка disk1/npm/letsencrypt сохраняет их между перезагрузками. Если сертификаты действительно исчезают — проверь что mount создан правильно и NPM реально пишет в disk1/npm/letsencrypt, а не куда-то внутрь контейнера.
Итог
У тебя теперь рабочий MikroTik с двумя контейнерами, SSL через Let’s Encrypt и reverse proxy для внутренних сервисов. Роутер стал не просто маршрутизатором — он терминирует TLS и публикует сервисы наружу. После перезагрузки всё поднимается само.
Главное что нужно помнить: порядок имеет значение. MariaDB первой, NPM вторым. Сеть настрой до контейнеров. tmpdir — на USB. Порт 80 должен быть открыт снаружи иначе Let’s Encrypt молчаливо упадёт.
Если что-то не работает — смотри логи контейнера сразу после попытки запуска. Логи в RouterOS хранятся недолго, не откладывай.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →



