Как мигрировать инфраструктуру с Docker на Podman - Академия Selectel

Как мигрировать инфраструктуру с Docker на Podman

Тирекс
Тирекс Самый зубастый автор
14 апреля 2026

На что стоит обратить внимание при переносе инфраструктуры с Docker на Podman и стоит ли вообще этим заниматься.

Изображение записи

Миграция на Podman — вопрос достаточно интересный. И ответ на него достаточно простой — берете и мигрируете, там делов-то! Но на самом деле это только верхушка айcберга. Сначала кажется, что все просто. А потом оказывается, что нужно решить много спорных моментов и учесть кучу мелочей.

Автор статьи — Роман Шубин, CTO и автор Telegram-канала Bash Days.

Зачем вообще это все

Когда балуетесь с сетью на сервере, настраиваете DNS и т. п., порой нужно перезагружать Docker daemon и пересоздавать все контейнеры. Потому что после применения хостовых настроек, Docker в большинстве случаев превращается в тыкву, а после перезагрузки демона все контейнеры останавливаются.

Ситуация страшная и точно не для продакшена. А поднимать рядом такой же сервер с нужными настройками и потом все это прозрачно переносить — слишком долго и на самом деле лень. Короче, Docker выступает единой точкой отказа всех сервисов, вне зависимости от их связанности. Ну и минус — работает вся эта штука от root, поэтому если ваш контейнер будет скомпрометирован и у злоумышленника руки растут из нужного места, ваша хостовая машина окажется в опасности.

Буквально пару недель назад этот случай я прочувствовал на себе. Поручил человеку поднять TMS для тестировщиков, оно все в compose завязано. В compose же прописано 6379:6378 и 5432:5432. Естественно, молодой специалист на это не обратил внимания, дополнительно даже не удосужился сменить дефолтные пароли. Выкатил в продакшен, спрятал за nginx. Но порты-то торчали!

 

А на следующий день пришли тестировщики и начали ругаться, что их TMS перестала работать. Пошли выяснять: злоумышленники залезли в контейнер, провели продуманную атаку и вышли на хостовую машину, превратив ее в зомби и ферму для майнинга криптовалюты.

 

Выводы сделаны. Перед запуском чего-то инородного, никогда не забывайте просматривать и менять дефолтные явки и пароли и дополнительно ограничивайте порты, даже так 127.0.0.1:5432. Тогда они не будут торчать наружу.

В общем, для начала нужно составить план действий. Собственно, с плана начинается любая подобная затея. Если у вас есть список действий, то вероятность ошибки сводится к минимуму. Ну а если у вас такого списка нет, значит вы уже прожженный сеньор-помидор, который работает под рутом, слава вам и почет.

Так, предположим план есть, это уже 90% сделанной работы. Осталось сделать остальные 90%.

Чем может помочь Podman, если мигрировать на него

Ну хотя бы тем, что в нем есть Quadlet, а эта штука нативно интегрирована с systemd. Получается, что Quadlet превращает все декларативные конфигурации в системные юниты. Каждый такой сервис будет управляться через systemd, иметь политику перезапуска, зависимости и свой жизненный цикл. Получается гибкая система: никаких демонов, нет единой точки отказа, контейнеры работают не под root, процессы полностью независимы, сетевые настройки не наследуются от хоста. И все это дело управляется через systemctl и journalctl. Звучит очень вкусно!

Ладно, перед миграцией неплохо было бы подготовить хостовую машину к Podman.

Несколько нехитрых действий

Разрешить обычному юзеру использовать привилегированные порты в rootless

Порты 0–1023 считаются привилегированными и их может открывать только root. Если вы запускаете контейнер от обычного пользователя, то контейнер не сможет открыть условный 80 порт, вылезет очевидная ошибка — permission denied.

Поэтому делаем финт ушами:


      echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/90-unprivileged-ports.conf >/dev/null
sudo sysctl -p /etc/sysctl.d/90-unprivileged-ports.conf

Теперь порты 0-79 может использовать только root, а все, что начинается с 80, можно использовать обычным пользователям.

Включить Linger

Он нужен для того, чтобы сказать системе: «Не останавливай процессы этого пользователя, даже если пользователь вышел из системы, например, закрыл терминал с SSH».


      sudo loginctl enable-linger $USER

После выполнения команды пользователь может иметь фоновые процессы. Они будут работать без активной сессии, а systemd будет запускать user manager при старте системы.

Если этого не сделать и запустить от пользователя podman run -d nginx, а затем закрыть SSH-терминал, контейнер помрет. С Linger контейнер продолжит работать.

Но тут нужно быть аккуратнее, так как процессы пользователя могут работать вечно. Даже если пользователь не залогинен, могут тратиться ресурсы.

Установить Podman

Дальше уже можно устанавливать сам Podman. Есть несколько вариантов установки, да еще и под разные дистрибутивы. Если у вас что-то Debian-like, то делайте так:


      sudo apt install podman

И при желании можно включить работу через socket:


      systemctl --user enable --now podman.socket

Podman может работать в двух режимах: CLI и API. В первом режиме Podman напрямую запустит контейнер, без демона. Это считается основным режимом:


      podman run nginx

Режим через socket позволяет подключаться к Docker API и использовать docker-compose, CI/CD-инструменты и какие-то сторонние клиенты:


      /run/user/UID/podman/podman.sock

Второй режим больше для галочки и каких-то экстравагантных случаев. По умолчанию рекомендую работать с первым (CLI) и не забивать себе голову всеми этими прокладками с API.

Конвертируем compose-файлы

В большинстве случаев для конвертации файлов docker-copmpose хватает утилиты podlet. Устанавливается она отдельно:


      curl -L https://github.com/containers/podlet/releases/download/v0.3.1/podlet-x86_64-unknown-linux-gnu.tar.xz -o podlet.tar.xz
 tar -xf podlet.tar.xz
 cd podlet-x86_64-unknown-linux-gnu/
 sudo mv podlet /usr/local/bin/
 sudo chmod +x /usr/local/bin/podlet
Скриншот терминала с проверкой версии утилиты podlet. Вывод команды podlet -V показывает версию 0.3.1.

Утилита удобная, но на практике надежнее все сделать руками. Как сделать руками, я показывать не буду, пусть это будет домашним заданием. Рассмотрим возможности podlet, а заодно — как все сделать быстро.

Пусть у нас есть условный файл docker-compose.yaml с содержимым:


      version: "3.9"

 services:
   web:
 	image: nginx:latest
 	ports:
   	- "8080:80"
 	restart: always

Нам нужно это преобразовать во что-то понятное для Podman:


      podlet -i -a compose docker-compose.yaml

На выходе получаем…

Скриншот терминала, демонстрирующий Quadlet-конфигурацию web.container. Описаны параметры образа nginx:latest, публикация порта 8080:80 и настройка автоматического перезапуска в секции [Service].

Получаем готовое содержимое для файла с юнитом для Quadlet. И этот тот самый случай, когда вручную было бы быстрее все сделать, чем ставить какой-то там конвертер.

С Quadlet мы уже познакомились в прошлых статьях про Podman (про healthcheck и добавление переменных), поэтому, надеюсь, вы обладаете нужной базой. Повторяться не буду, чтобы не раздувать материал.

Теперь давайте рассмотрим проблемную ситуацию:


      version: "3.9"

 services:
   caddy:
 	image: caddy:latest
 	ports:
   	- "80:80"
 	networks:
   	- caddy_net

 networks:
   caddy_net:
 	external: true

Конвертируем:


      podlet -i -a compose docker-compose.yaml

Опа, и получаем ошибку:

Скриншот терминала с ошибкой выполнения команды podlet. Текст ошибки сообщает, что внешние сети (external networks, в данном случае 'caddy_net') не поддерживаются при конвертации в Quadlet-файлы.

А не все так просто. Чтобы пофиксить, нужно удалить полностью блок networks, чтобы получилось так:


      version: "3.9"

 services:
   caddy:
 	image: caddy:latest
 	ports:
   	- "80:80"

Теперь конвертация пройдет успешно:

Скриншот терминала с результатом генерации Quadlet-файла caddy.container. Показаны параметры [Container] с образом caddy:latest и пробросом портов PublishPort=80:80.

А чтобы добавить network, редактируем юнит вручную:


      [Container]
 Image=docker.io/library/caddy:latest
 PublishPort=80:80
 Network=caddy.network

 [Service]
 Restart=always

 [Install]
 WantedBy=default.target

А сам файл caddy.network может выглядеть так:


      [Unit]
 Description=Caddy network

 [Network]
 Driver=bridge

 [Install]
 WantedBy=default.target

Тут все зависит от того, что вы переносишь и как сконфигурированы у вас сети. Можно просто оставить блок [Network] — тогда создастся сеть с настройками по умолчанию, без излишеств.

Теперь смотрим вариант с несколькими сервисами:


      services:
   app:
 	image: myapp
 	depends_on:
   	- db

   db:
 	image: postgres
Скриншот терминала, показывающий вывод команды podlet для двух контейнеров: app.container и db.container. В секции [Unit] для приложения указаны зависимости Requires=db.service и After=db.service.

Как видим, podlet разделил конфигурации под разные юниты, все логично. Но не забываем, что в Podman нет явного depends_on. Как это решать, я рассказывал в статье про зависимости.

Это базовая база podlet-функционала, на первое время его вполне достаточно для миграции. Да, оно не идеальное, блоки с сетями порой ломаются, лезут какие-то артефакты. Но для старта сгодится.

С переменными и маунтами тоже все хорошо:

Скриншот терминала с результатом выполнения команды podlet для конвертации Docker Compose в Quadlet-файл nginx.container. Видны секции [Container] с образом vaultwarden/nginx, [Service] с политикой Restart=always и [Install].

Они конвертятся в юниты без особых проблем.

Что еще стоит учитывать

Ручное вмешательство мы рассмотрели, автоматизировать получится не все. Есть еще множество подводных камней, которые могут вылезти при миграциях. Давайте рассмотрим основные, с которыми я сталкивался.

Все сложно

Скажем, бывают compose-файлы, в которых описаны контейнеры, в которых используется docker socket для управления другими контейнерами.

Мем, в котором человек что-то усердно считает на пальцах.
Файлы, в которых контейнеры, в которых socket, который управляет другими контейнерами.

Например в Nextcloud AIO. Тут придется поглубже копнуть логику с юнитами и ничего не сломать. Ситуация редкая, но порой встречается.

Нет systemd

В некоторых Linux-дистрибутивах вообще может не быть systemd, а есть только open-rc и runc. Под такое конвертатор явно не заточен и все придется делать ручками. Так что если надумаете мигрировать в Podman, учитывайте этот момент и выбирайте дистрибутив, который работает с systemd.

Перенос Portainer

Не всегда очевидно, как перенести тот же Portainer, который привык работать напрямую с сокетом docker, и без root превращается в тыкву. Да, это все накручивается и обходится, но нужно время и мало-мальски какие-то знания.

Безопасность

Если у вас безопасность не на первом месте, продолжайте использовать Docker и не мучайтесь с этим всем. В крайнем случае поставьте k3s и крутите контейнеры в нем. Заодно хоть немного приблизитесь к Kubernetes и начнете нарабатывать практику.

Короче, поменьше читайте всякого в интернете и побольше практикуйтесь. Просто ставите цель, берете и начинаете потихоньку мигрировать. На ходу уже фиксите баги, делаете заметки, мотаете на ус. Вероятно, лучше так не делать в проде, а учиться в каком-нибудь тестовом окружении, а то мало ли.

Заключение

Как по мне, в миграции на Podman одни плюсы:

  • Podman будет кушать поменьше RAM и CPU;
  • под капотом больше не будет громоздкого Docker daemon, который при перезапуске положит разом весь продакшен;
  • каждый контейнер станет отдельным процессом, который будет работать от обычного пользователя;
  • образы будут совместимы, ничего не придется пересобирать;
  • масса удовольствия в процессе миграции.

Единственный момент — придется все же вникнуть в rootless-режим. Ну и с привилегированными портами не попасть в просак. А так на самом деле все очевидно и просто.

Хорошего дня!