Миграция с Docker на rootless Podman: когда оправдано и как сделать
На восьми серверах Dell Xeon Platinum 8280 в московском дата-центре МТС одновременно работают сотни клиентских контейнеров. Последние два года мы переводим часть продакшн-нагрузки с Docker на rootless Podman — не повсеместно, а там, где это даёт реальные преимущества в безопасности и бесшовной интеграции с systemd. Дальше — опыт этой миграции: что прошло гладко, а где споткнулись.
Зачем вообще менять Docker
У Docker есть одна архитектурная проблема: демон работает от root. Любой пользователь, состоящий в группе docker, фактически получает root-доступ к хосту — может через -v /:/mnt смонтировать файловую систему и писать куда угодно. Для мультитенантных серверов это бесполезный уровень изоляции.
Podman rootless работает иначе:
- Главное отличие: нет демона. Контейнеры здесь — это просто обычные процессы пользователя, которыми управляют напрямую через fork/exec.
- Каждый пользователь имеет свой маппинг UID через
/etc/subuidи user namespaces. - Интеграция с systemd из коробки (
systemctl --user, quadlet). - OCI-совместимость — это очень удобно. Используйте те же самые образы, к которым вы привыкли в Docker Hub. Никаких проблем с совместимостью.
Когда НЕ надо мигрировать
Вот что я сразу хочу сказать всем нашим клиентам: нет никакого смысла затевать миграцию просто ради самой миграции. Зачем что-то менять, если и так всё отлично работает? Смело оставайтесь на Docker, если:
- Если у вас всего один сервер, вы — root-админ, и всё и так прекрасно работает, зачем что-то трогать?
- Если вы используете Docker Swarm для оркестрации, сразу скажу: Podman его не умеет. Тут выбор очевиден.
- Требуются серьёзные сетевые привилегии для приложения? Например, VPN или raw-сокеты? Rootless Podman здесь сильно ограничен, имейте в виду.
- Если ваш CI/CD-процесс глубоко завязан на Docker-in-Docker, то миграция станет настоящей головной болью. А возможно, и вовсе нецелесообразной.
Так когда же мигрировать? Если ваш сервер общий для разных разработчиков и вам нужна надёжная изоляция. Или когда без компромиссов есть жёсткие требования по безопасности, например, по ФСТЭК/ISO. И, конечно, если вы уже используете RHEL/Rocky 9 — там Podman буквально "родной", встроен в систему.
Установка Podman
# Rocky Linux 9 / RHEL 9
sudo dnf install -y podman podman-compose
# Ubuntu 24.04
sudo apt install -y podman podman-compose
# Проверка
podman version
podman info | grep -i rootless
Для rootless нужны /etc/subuid и /etc/subgid. В свежих дистрибутивах заводятся автоматически при создании пользователя. Проверить:
grep $USER /etc/subuid /etc/subgid
# Должно быть что-то вроде
# /etc/subuid:operator:100000:65536
Docker → Podman: словарь команд
| Docker | Podman | Комментарий |
|---|---|---|
| docker run | podman run | Совместимо на 99% |
| docker ps | podman ps | Одинаково |
| docker build | podman build | Используется buildah внутри |
| docker-compose up | podman-compose up | Или quadlet-файлы |
| docker network | podman network | Под капотом netavark/CNI |
| docker volume | podman volume | Хранилище в ~/.local/share/containers |
Я всегда делаю в ~/.bashrc:
alias docker=podman
alias docker-compose="podman-compose"
Миграция compose-проекта
Берём существующий docker-compose.yml. Большинство работает без правок. Что часто ломается:
- Порты ниже 1024. Rootless не может слушать 80/443 напрямую. Либо переназначайте на 8080/8443 + ставьте nginx на хосте, либо правьте
sysctl net.ipv4.ip_unprivileged_port_start=80. - Bind-mount с абсолютными путями. Из-за user namespace UID внутри и снаружи могут не совпадать. Проверяйте права.
- privileged-контейнеры. В rootless часть флагов ограничена. Перечитайте, нужен ли реально privileged.
cd /opt/app
podman-compose up -d
podman-compose ps
Quadlet: systemd-юниты для контейнеров
Это моя любимая фича Podman 4.4+. Вместо compose-файла описываете контейнер в systemd-юните *.container, и systemd сам поднимает его при старте.
# ~/.config/containers/systemd/nginx.container
[Unit]
Description=Nginx reverse proxy
After=network-online.target
[Container]
Image=docker.io/library/nginx:1.27-alpine
PublishPort=8080:80
Volume=/home/operator/nginx/conf:/etc/nginx/conf.d:ro,Z
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user start nginx
journalctl --user -u nginx -f
Плюсы: логи в journalctl, автозапуск через loginctl enable-linger, обновление через podman auto-update по расписанию.
Quadlet для pod: несколько контейнеров в одной сети
Когда сервис состоит из нескольких контейнеров, которым нужна общая сеть и общий порт, удобнее объединить их в pod — как в Kubernetes. Quadlet поддерживает это через файлы .pod и привязку контейнеров к pod.
# /etc/containers/systemd/app.pod
[Pod]
PodName=myapp
PublishPort=8080:80
PublishPort=5432:5432
# /etc/containers/systemd/app-nginx.container
[Container]
Pod=myapp.pod
Image=nginx:alpine
Volume=/opt/app/nginx.conf:/etc/nginx/nginx.conf:ro,Z
# /etc/containers/systemd/app-db.container
[Container]
Pod=myapp.pod
Image=postgres:17-alpine
Environment=POSTGRES_PASSWORD=secret
Volume=app-pgdata.volume:/var/lib/postgresql/dataПосле создания файлов выполните systemctl daemon-reload и запустите pod: systemctl start myapp. Все контейнеры внутри pod стартуют и останавливаются как единое целое, а логи каждого контейнера по-прежнему доступны через journalctl -u app-nginx -f.
Реальный кейс: мониторинговый стек для производственного холдинга
В октябре 2025 года клиент — металлообработка на 45 рабочих местах — пришёл с задачей: «хотим Grafana + Prometheus + Loki, но служба безопасности запрещает запускать сервисы от root». Идеальный кейс для Podman rootless. Мы создали отдельного пользователя monitor, дали ему subuid 100000:65536, развернули 8 контейнеров через quadlet.
Сроки: 2 рабочих дня на развёртывание, 1 день на интеграцию с экспортёрами на 6 серверах клиента. Стоимость — 54 000 руб. Через полгода заказчик отметил: zero инцидентов безопасности с контейнерами, обновления по расписанию через podman auto-update, единый журнал в journalctl.
Автообновление образов: podman auto-update
Podman умеет автоматически обновлять контейнеры при появлении нового образа в registry. В Quadlet для этого достаточно указать AutoUpdate=registry в секции [Container], либо при ручном запуске добавить метку --label io.containers.autoupdate=registry.
# Проверка обновлений без применения (dry-run)
podman auto-update --dry-run
# Применить обновления
podman auto-update
# Включить systemd-таймер для ежедневной проверки
systemctl enable --now podman-auto-update.timer
# По умолчанию — ежедневно в 00:00Это удобно для стейтлес-сервисов: nginx, приложения, воркеры. Для баз данных автообновление лучше отключить — обновление PostgreSQL или MySQL требует ручной миграции данных.
Сетевые грабли
В rootless сеть отличается от Docker bridge. Используется slirp4netns или pasta для user-mode networking. Следствия:
- Источник соединений снаружи виден как
10.0.2.100, а не реальный IP клиента. Для nginx логов ставьте--network=pasta:--map-guest-addr,169.254.1.2или используйте реверс-прокси на хосте. - Скорость сети. В rootless-режиме она заметно ниже, чем в root-mode. По нашим собственным тестам, это около 40% потери. Имейте в виду.
- Мульти-хостовые сети (overlay)? Штатной поддержки нет. Придётся обходиться отдельными "костылями" — это не всегда удобно.
Но если мы говорим о высокопроизводительных сервисах — например, о базах данных или видеостримах, тут всё иначе. В таких случаях лучше либо совсем не трогать Docker, либо уж сразу идите на rootful Podman. Полумеры здесь просто не работают.
Проверка user mapping и сканирование образов
Чтобы убедиться, что rootless-изоляция действительно работает, проверьте маппинг UID внутри user namespace:
podman unshare cat /proc/self/uid_map
# 0 1000 1 ← root в контейнере = uid 1000 на хосте
# 1 100000 65536 ← остальные uid-ы замаплены в безопасный диапазонДля контроля безопасности образов интегрируйте сканер уязвимостей Trivy:
# Получить digest образа
podman image inspect --format '{{.Digest}}' myapp:latest
# Сканирование на CVE
trivy image myapp:latestВ rootless-режиме SELinux и AppArmor работают из коробки, а seccomp-профили по умолчанию блокируют опасные системные вызовы — дополнительная настройка не требуется.
Миграционный чек-лист
- Что ещё важно? Инвентаризация контейнеров. Нужно чётко понимать, что запущено, какие ресурсы потребляет и какие порты открыты.
- Наш совет: прежде чем делать что-то глобально, обязательно протестируйте миграцию на одном второстепенном сервисе. Поможет выявить большинство проблем заранее.
- Сначала самое главное: ставим Podman. Да, прямо рядом с вашим Docker, пусть пока поживут вместе. Это позволит нам всё протестировать безболезненно. А чтобы всё работало без лишних прав, обязательно создаём нового пользователя для rootless-режима. Безопасность — это важно, не так ли?
- Теперь займёмся файлами `compose`. Просто так перенести их не получится. Нужно адаптировать настройки — особенно внимательно посмотрите на порты, опции `privileged` и `bind-mounts`. В Podman они работают немного иначе, поэтому придётся кое-что подправить, чтобы всё встало на свои места. Это как переезд в новую квартиру: мебель та же, но расставлять надо по-другому.
- Переезд образов (
podman pull) и данных (томов). - Итак, всё готово к запуску! Используем `podman-compose` или современный `quadlet` — смотря что вам ближе. Но главное, не торопитесь. Мы рекомендуем запускать Podman параллельно с Docker на 3–5 дней. За это время вы сможете убедиться, что все сервисы работают стабильно, а ошибок нет. Ведь спешка здесь ни к чему.
- Отлично, тестовый период пройден? Всё работает как часы? Тогда можно с чистой совестью отключать Docker-контейнеры. И, пожалуйста, не забудьте про архивацию данных! Это как собрать вещи после переезда — вдруг что-то понадобится. Лучше перестраховаться, чем потом кусать локти.
- Удаление Docker (если больше не нужен).
- Всё, миграция завершена. Но работа на этом не заканчивается! Чтобы система жила долго и счастливо, настройте автообновление, регулярные бэкапы и, конечно, мониторинг. Мы же не хотим сюрпризов, правда? Пусть всё будет под контролем. Надёжность и стабильность — вот к чему мы стремимся.
FAQ — переход с Docker на Podman
- Чем rootless Podman безопаснее Docker?
- Docker-демон запускается от root и эффективно даёт root-доступ всем, кто в группе docker. Podman rootless работает под обычным пользователем с user namespaces — уязвимость в контейнере даёт права пользователя, не root. Нет демона — нет единой точки отказа.
- Podman совместим с Docker CLI?
- Да, alias docker=podman работает для 90% команд: pull, run, ps, exec, logs, build. Отличия — в сетевых командах (podman использует netavark/slirp4netns) и в рабочих каталогах. Скрипты обычно переезжают с минимальными правками.
- Нужен ли podman-compose или лучше сразу quadlet?
- Для быстрой миграции — podman-compose, он читает docker-compose.yml. Для продакшна рекомендую quadlet — systemd-юниты с описанием контейнеров, чистая интеграция с journalctl, автозапуск при перезагрузке.
- Можно ли пробросить порты 80 и 443 в rootless?
- По умолчанию нет — rootless пользователь не имеет права на привилегированные порты. Решения: net.ipv4.ip_unprivileged_port_start=80 в sysctl, setcap, либо reverse proxy (nginx/haproxy) на хосте, а контейнеры слушают 8080/8443.
- Podman поддерживает GPU и CUDA?
- Да, через CDI (Container Device Interface). NVIDIA предоставляет nvidia-container-toolkit с поддержкой Podman. Настраивается чуть сложнее Docker, но работает стабильно на RHEL 9, Rocky 9, Ubuntu 24.04.

