SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-26

Podman или Docker на VPS: что выбрать для сервера

Узнайте ключевые различия в архитектуре Podman и Docker. Разберем работу без демона, настройку прав доступа к томам, использование Quadlet и особенности привязки портов на VPS.

В чем заключается реальная разница между Podman и Docker

Podman и Docker запускают одни и те же образы OCI (open container initiative) на VPS, поэтому выбор не ограничивает набор доступного ПО. Разница заключается в модели процессов. Docker запускает root-демон, который владеет всеми контейнерами, а команда docker является лишь небольшим клиентом, который запрашивает выполнение работы у этого демона. У Podman нет демона: podman run запускает контейнер как дочерний процесс того, кто его вызвал, от имени вашего непривилегированного пользователя.

Все остальное вытекает из этого факта. Автозапуск становится задачей systemd, а не демона. Владение томами передается через пространство имен пользователя (user namespace), поэтому владелец, которого вы видите с помощью ls -l на хосте, не совпадает с владельцем, которого видит контейнер. Порты ниже 1024 не будут привязываться, пока вы не измените настройки ядра. Интерфейс командной строки docker продолжает работать через обертку вплоть до момента, когда приложению требуется доступ к сокету Docker.

Отсутствие демона: что на самом деле выполняется при запуске контейнера

На хосте Docker команда pstree -a показывает dockerd с правами root, процесс containerd рядом с ним и по одному процессу containerd-shim-runc-v2 на каждый запущенный контейнер. Ваше приложение является дочерним процессом этого shim, а сам shim — дочерним процессом PID 1. Контейнер никак не связан с оболочкой, которая его запустила. Если остановить демон, вы потеряете панель управления всеми контейнерами на сервере, а при выключенной настройке live-restore по умолчанию команда systemctl restart docker также перезапустит ваши контейнеры.

В Podman нет аналогичного процесса. При запуске контейнера вы получаете один процесс conmon (монитор контейнера), который удерживает основной процесс контейнера и принадлежит пользователю, выполнившему команду.

podman run -d --name web -p 8080:80 docker.io/library/caddy:2
ps -o user,pid,ppid,args -C conmon
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080

Команда ps должна показать conmon, запущенный от имени вашего пользователя, а не от root, а curl должна вывести 200. Поскольку контейнером не управляет централизованная служба, команда sudo apt upgrade podman не останавливает ничего из того, что уже запущено, и сбой монитора одного контейнера не приводит к падению остальных.

Отсутствие демона имеет и свои недостатки. Ничто не запускает ваши контейнеры автоматически после перезагрузки. Настройка --restart=always в Docker — это обязательство, которое демон выполняет при загрузке системы; в Podman её заменяет systemd, для чего и предназначен раздел о quadlet ниже.

Сокет — это вторая часть истории. /var/run/docker.sock представляет собой API (интерфейс прикладного программирования), принадлежащий пользователю root, и любой процесс, имеющий права на запись в него, может запустить привилегированный контейнер, который смонтирует файловую систему хоста. Добавление пользователя в группу docker предоставляет этому пользователю права root более медленным путем, что стоит изучить в дополнение к предоставлению каждой сервисной учетной записи только необходимых прав доступа. Podman не открывает сокет, пока вы сами его не запросите, а полученный сокет принадлежит конкретному пользователю по пути /run/user/<uid>/podman/podman.sock.

Установка Podman на Ubuntu 24.04 и проверка работы в режиме rootless

sudo apt update
sudo apt install -y podman uidmap
podman --version
podman info | grep -i rootless

Пакет uidmap предоставляет newuidmap и newgidmap. Это setuid-утилиты, которые позволяют обычному пользователю задействовать диапазон подчиненных идентификаторов (subordinate IDs); без них rootless-контейнеры не запускаются. Команда podman info должна вывести rootless: true.

В Ubuntu 24.04 поставляется Podman 4.9, а в Debian 13 — Podman 5.x (данные на август 2026 года). Эта разница важна, так как для quadlet-файлов требуется версия 4.4 или новее, а для .pod quadlet-файлов — версия 5.0. Выполните podman --version перед тем, как копировать примеры из официальной документации.

Каждому пользователю в режиме rootless требуется диапазон подчиненных идентификаторов:

grep "$USER" /etc/subuid /etc/subgid

Пользователь, созданный командой adduser в Ubuntu, получает такой диапазон автоматически. Пользователь, созданный через useradd -M или с помощью средств автоматизации, часто его не имеет, и при попытке запуска возникает ошибка:

Error: cannot find UID/GID for user deploy: no subuid ranges found for user "deploy" in /etc/subuid - check rootless mode in man pages.

Назначьте диапазон, а затем сбросьте хранилище пользователя, чтобы применилось новое отображение:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrate

Еще один нюанс при первом запуске: Podman не предполагает использование Docker Hub по умолчанию. Короткое имя образа разрешается через unqualified-search-registries в файле /etc/containers/registries.conf, и в скрипте без привязанного терминала загрузка завершится с ошибкой short-name resolution enforced but cannot prompt without a TTY. Всегда указывайте полное имя образа. Используйте docker.io/library/nginx:1.27 вместо nginx.

Что на самом деле дают rootless-контейнеры на арендованном сервере

Rootless-контейнер работает внутри пространства имен пользователя (user namespace) — функции ядра, которая предоставляет процессу собственную изолированную карту идентификаторов пользователей (UID). Внутри этого пространства суперпользователь контейнера имеет UID 0. Снаружи, на вашем VPS, этот же процесс выполняется от имени вашего обычного пользователя. Root внутри контейнера не является root на хостовой системе.

Это и есть реальный масштаб преимущества. Образ, который требует запуска от имени root, веб-приложение с уязвимостью удаленного выполнения кода или попытка побега из контейнера, зависящая от наличия UID 0 во внешней системе — всё это в итоге будет ограничено правами вашего непривилегированного пользователя, а не правами всей машины. Rootless не защищает от ошибок в ядре и не защищает ваши личные файлы, так как процесс, вышедший за пределы контейнера, выполняется от вашего имени и может читать всё, что доступно вам. Изоляция конкретной единицы (unit) важна не меньше, чем маппинг UID, что нагляднее реализовано в FreeBSD jail, который оборачивает целое пользовательское окружение, администрируемое как отдельная машина, а не просто слоистый образ, загруженный из реестра.

Docker также может работать в режиме rootless. dockerd-rootless-setuptool.sh install настраивает демона для каждого пользователя, и это работает эффективно. Разница заключается в настройках по умолчанию. В Podman режим rootless включен изначально, поэтому первой проблемой, с которой вы столкнетесь, будет невозможность контейнера занять 80 порт, вместо сервиса, который два года незаметно работал с правами root.

Почему мои файлы в томах принадлежат UID 100999?

Это происходит из-за использования пространства имен пользователей (user namespace). UID 0 в контейнере отображается на ваш UID в хост-системе. UID 1 в контейнере отображается на первый идентификатор в вашем диапазоне subuid, и далее отсчет идет по порядку. Если диапазон начинается с 100000, то UID 1000 из контейнера на хосте превращается в 100999.

mkdir -p "$PWD/data"
podman run --rm --user 1000 -v "$PWD/data:/data" docker.io/library/alpine:3 sh -c 'id -u; touch /data/f'
ls -ln "$PWD/data"

Контейнер выводит 1000. В листинге на хосте владельцем значится 100999, так как 100000 плюс 1000 минус 1 равно 100999. Ничего не сломано, и обычная команда chown не исправит ситуацию, так как ваш непривилегированный пользователь не может изменять владельца файлов за пределами пространства имен.

Четыре способа решения:

  • podman unshare chown 1000:1000 "$PWD/data" выполняет chown внутри того же пространства имен пользователей, где числа имеют то же значение, что и для контейнера.
  • -v "$PWD/data:/data:U" просит Podman автоматически исправить владельца исходного каталога. Используйте этот флаг для новых каталогов, а не для важных данных.
  • --userns=keep-id отображает ваш UID на хосте на такой же UID внутри контейнера, поэтому новые файлы будут принадлежать вам.
  • Именованный том, такой как -v appdata:/data, позволяет избежать этого вопроса, так как Podman создает его в собственном хранилище с уже корректными правами владения.

Если вы сталкивались с этим в Docker, то это та же проблема, но на уровень выше. Переменные PUID и PGID, которые предоставляют многие образы задают UID, используемый процессом внутри контейнера, а в rootless Podman этот UID затем отображается повторно. PUID=1000 внутри rootless-контейнера по-прежнему создает на хосте файлы, принадлежащие 100999. Выбирайте числа с учетом этого вторичного отображения или перенесите данные в именованный том и перестаньте об этом беспокоиться.

Еще два примечания о монтировании. Флаги :z и :Z, которые встречаются в примерах для Fedora и RHEL, являются опциями перемаркировки SELinux; в Ubuntu используется AppArmor, поэтому там они не выполняют никаких действий. Кроме того, rootless Podman не может смонтировать каталог хоста, который недоступен для чтения вашему пользователю, и это является преимуществом безопасности, а не ошибкой.

Почему rootless Podman отказывается публиковать порт 80?

Потому что для привязки порта с номером ниже 1024 требуются привилегии, которых нет у вашего пользователя. Текст ошибки указывает на способ исправления:

Error: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission denied

Существует два подходящих решения. Можно снизить порог для всей системы:

echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system
sysctl net.ipv4.ip_unprivileged_port_start

Последняя команда должна вернуть 80. Чётко осознавайте последствия этой настройки: теперь любой пользователь на машине может привязаться к портам 80 и 443, а не только тот, кто запускает контейнеры. На VPS с одним администратором это допустимый компромисс. На сервере, где работают другие пользователи, — нет. Второй вариант — публиковать порт 8080 и поставить перед ним reverse proxy, где вы в любом случае будете выпускать и обновлять сертификаты через certbot на nginx.

Rootless-публикация также меняет то, как приложение видит входящие соединения. Podman 4.x по умолчанию использует slirp4netns через обработчик портов rootlesskit, поэтому перенаправленные соединения приходят с подменённым адресом источника, и в access log каждый посетитель отображается как 10.0.2.100. В Podman 5.0 значение по умолчанию изменено на pasta, что позволяет сохранить реальный адрес клиента. В версии 4.x --network slirp4netns:port_handler=slirp4netns восстанавливает истинный адрес источника, но это несколько снижает пропускную способность.

Здесь есть один положительный момент. Rootless-порт — это обычный слушающий сокет, принадлежащий обычному процессу, поэтому к нему применяются правила входящего трафика вашего межсетевого экрана. Docker публикует порты, записывая правила NAT (network address translation) и собственные разрешения на пересылку, что является прямой причиной того, почему опубликованный порт Docker игнорирует правило ufw, которое, как вы думали, его блокирует. Rootful Podman использует аналогичные механизмы и наследует ту же ловушку. Rootless — нет.

Будут ли мои файлы Docker Compose работать в Podman?

В основном да, двумя способами. Первый — это podman-compose, отдельная реализация, которая читает тот же файл и управляет CLI Podman:

sudo apt install -y podman-compose
podman-compose up -d
podman ps

Второй — использование настоящего Docker Compose, который взаимодействует с Docker-совместимым API Podman через сокет пользователя:

systemctl --user enable --now podman.socket
export DOCKER_HOST="unix:///run/user/$(id -u)/podman/podman.sock"
docker compose up -d
podman ps

docker compose ps и podman ps должны выводить список одних и тех же контейнеров, так как набор контейнеров один. Разрешение имен также работает: сетевой бэкенд Podman по умолчанию, netavark, запускает aardvark-dns, поэтому контейнеры в пользовательской сети находят друг друга по именам.

Существуют нюансы. Всё, что монтирует /var/run/docker.sock, должно быть перенаправлено на сокет Podman или удалено. network_mode: host ведет себя иначе в пространстве имен пользователя (user namespace). depends_on с condition: service_healthy поддерживается неравномерно в разных версиях podman-compose. restart: always не сохраняется после перезагрузки автоматически, что исправляется в следующем разделе. Compose остается удобным способом описания стека из нескольких контейнеров в одном файле, а в Podman он выступает в роли уровня трансляции. Если вы планируете использовать стек в течение многих лет, преобразуйте его в quadlets, чтобы поддерживать одну абстракцию вместо двух.

Pods: концепция, на которую у Docker нет ответа

Pod — это группа контейнеров, использующих общее сетевое пространство имен (network namespace). Podman запускает небольшой контейнер infra, чтобы поддерживать это пространство имен открытым, после чего участники группы взаимодействуют друг с другом через 127.0.0.1 без необходимости настройки пользовательских сетей или использования механизмов service discovery.

podman pod create --name app -p 8080:80
podman run -d --pod app --name app-cache docker.io/library/redis:7
podman run -d --pod app --name app-web docker.io/library/nginx:1.27
podman pod ps
podman ps --pod

Команда podman pod ps должна показать pod Running с тремя контейнерами, включая инфраструктурный. Теперь web-контейнер обращается к Redis по адресу 127.0.0.1:6379, а не app-cache:6379. Из общего пространства имен следуют два правила: порты публикуются на уровне pod, а не отдельных контейнеров, и никакие два контейнера внутри одного pod не могут слушать один и тот же порт.

Это модель Kubernetes, и Podman активно её использует. podman kube generate app > app.yaml генерирует манифест Kubernetes на основе запущенных объектов (в старых версиях пакетов команда называется podman generate kube), а podman kube play app.yaml позволяет воссоздать их на другом хосте. В Quadlet предусмотрен тип юнита .kube, который запускает такой файл как службу systemd. Это принципиально иной способ группировки сервисов, и он является весомым аргументом в пользу выбора Podman, если в будущем вы планируете использовать Kubernetes.

Автозапуск без демона: юниты Quadlet

Quadlet — это генератор systemd. Он превращает короткий файл с описанием контейнера в полноценный systemd-сервис при загрузке системы. Файлы размещаются в ~/.config/containers/systemd/ для rootless-пользователя или в /etc/containers/systemd/ для root.

~/.config/containers/systemd/caddy.container:

[Unit]
Description=Caddy web server
After=network-online.target

[Container]
Image=docker.io/library/caddy:2
ContainerName=caddy
PublishPort=8080:80
Volume=caddy-data.volume:/data
Environment=TZ=UTC
AutoUpdate=registry

[Service]
Restart=always
MemoryMax=512M

[Install]
WantedBy=default.target

~/.config/containers/systemd/caddy-data.volume может быть почти пустым, так как заголовок секции сам создает том:

[Volume]
systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50

Имя сервиса берется из имени файла: caddy.container превращается в caddy.service. Не запускайте systemctl --user enable caddy. Сгенерированные юниты нельзя включить через enable, и systemd ответит Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated.. Секция [Install] отвечает за запуск контейнера при загрузке, а daemon-reload пересоздает юнит после внесения изменений в файл.

Теперь настройка, на которой ошибаются почти все:

sudo loginctl enable-linger deploy
loginctl show-user deploy --property=Linger

Ожидайте Linger=yes. Без linger systemd завершает всю пользовательскую сессию при закрытии последнего SSH-соединения, поэтому все rootless-контейнеры останавливаются вместе с ней и не запускаются при загрузке. Если контейнеры исчезают после выхода из системы, причина всегда в этом.

Так как контейнер является основным процессом обычного сервисного юнита, к нему напрямую применяются средства управления systemd. MemoryMax= и CPUQuota= в секции [Service] работают точно так же, как и для любого другого сервиса, который вы ограничиваете через systemd. Для этого требуется cgroup v2 (контрольные группы версии 2), которые используются в Ubuntu по умолчанию начиная с версии 22.04. Проверьте это с помощью podman info | grep -i cgroup.

Для обновлений предусмотрен механизм сопоставления. AutoUpdate=registry вместе с systemctl --user enable --now podman-auto-update.timer проверяет реестр на наличие более свежего образа с тем же тегом, перезапускает юнит и выполняет откат к предыдущему образу, если новый контейнер не запустился. Сначала выполните podman auto-update --dry-run, чтобы увидеть, какие изменения будут внесены. Старая команда podman generate systemd всё ещё существует, но она считается устаревшей, поэтому для всех новых задач пишите quadlet-файлы.

Где работает псевдоним docker, а где нет

sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker ps

podman-docker устанавливает обертку /usr/bin/docker, которая вызывает Podman. Без файла nodocker каждый вызов сначала выводит Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.. Обертка охватывает команды, которые вы используете ежедневно: run, ps, logs, exec, build, pull, push, inspect, cp, volume, network.

Список того, что не переносится, короче, но важнее. В Swarm mode нет эквивалента, поэтому стеку Swarm негде развернуться. Инструментам, которые обращаются к Docker socket, требуется экспорт Podman socket, и некоторые из них всё равно замечают разницу; Docker provider в Traefik работает, если указать ему /run/user/<uid>/podman/podman.sock, в то время как для Watchtower места нет, поскольку эту задачу выполняет podman auto-update. Хранилища разделены, поэтому Podman не видит образы, которые вы уже загрузили через Docker, и podman images на загруженном Docker-хосте поначалу будет пустым.

Пошаговая миграция работающего стека

  1. Создайте или выберите непривилегированного пользователя, который будет владельцем контейнеров, и убедитесь, что для него задан диапазон в /etc/subuid.
  2. Повторно загрузите все образы из реестра, используя полные имена. Podman имеет собственное хранилище образов и не считывает данные Docker.
  3. Перенесите локально собранные образы с помощью docker save app:1.4 | podman load.
  4. Остановите контейнер Docker, скопируйте содержимое каждого тома из /var/lib/docker/volumes/<name>/_data, затем исправьте права владения с помощью podman unshare chown -R 1000:1000 <path>.
  5. Решите вопрос с портами: публикуйте их выше 1024 за обратным прокси-сервером или настройте net.ipv4.ip_unprivileged_port_start.
  6. Создайте по одному файлу quadlet для каждого контейнера, выполните systemctl --user daemon-reload и запустите каждый сервис.
  7. Выполните sudo loginctl enable-linger <user>, перезагрузите VPS, войдите в систему снова и проверьте, что podman ps снова отображает все сервисы.

Эти два движка не имеют общих ресурсов: у них раздельные хранилища образов и разные сети. Поэтому вы можете запускать их одновременно во время миграции, и единственное, из-за чего они могут конфликтовать — это номера портов хоста. Перенесите один сервис, наблюдайте за ним в течение дня, затем переходите к следующему.

Podman или Docker: что выбрать для вашего VPS?

Оставайтесь на Docker, если ваш стек описан в compose-файлах, которые поддерживают другие люди, или если вы используете инструменты, взаимодействующие с Docker socket. Совместимость с тем, что используют остальные — это реальное преимущество, и у Docker её больше. Команда, у которой на ноутбуках установлен Docker, получает практическую пользу от запуска того же движка в production.

Переходите на Podman, если на VPS работает несколько сервисов, которые вы полностью контролируете, или если вы хотите запускать каждое приложение от имени отдельного непривилегированного пользователя без группы docker в системе. Соответствие дистрибутиву также имеет значение: RHEL и его клоны поставляют Podman как поддерживаемый движок, поэтому в таких системах Podman — это путь с наименьшим количеством сюрпризов. Если вы всё же хотите использовать Docker на одном из таких хостов, путь через dnf в Rocky Linux и AlmaLinux начинается с удаления обертки podman-docker, которая уже занимает команду docker. Если вы уже управляете всем остальным через systemd units, quadlets покажутся вам недостающим элементом, а не новым инструментом для изучения.

Стоит упомянуть и промежуточный вариант. Rootful Podman ведет себя почти как Docker, сохраняет команду docker через обертку и при этом избавляется от постоянно работающего демона. Однако при этом теряется преимущество rootless-режима, который и меняет вашу модель безопасности, поэтому рассматривайте это лишь как временный этап.

Если вы только настраиваете свой первый хост для контейнеров, путь установки и защиты Docker на чистом VPS будет короче, и эти знания не пропадут даром. Образы и тома являются одинаковыми объектами для обоих движков, поэтому последующий переход изменит лишь способ управления вашими сервисами, и практически ничего больше.

FAQ

Является ли Podman полной заменой Docker?

В плане команд — почти. Установка podman-docker предоставляет обертку /usr/bin/docker, а run, ps, build, logs и exec работают аналогично. Однако это не замена демону. У Swarm нет аналога, инструменты, подключающиеся к /var/run/docker.sock, должны быть перенаправлены на пользовательский сокет Podman, а образы, загруженные через Docker, остаются невидимыми для Podman, так как они используют разные хранилища.

Почему мои rootless-контейнеры Podman останавливаются после выхода из SSH?

Потому что systemd завершает пользовательскую сессию, а вместе с ней и все пользовательские службы, когда закрывается последний сеанс входа. Выполните sudo loginctl enable-linger <user>, затем убедитесь, что loginctl show-user <user> --property=Linger выводит Linger=yes. Параметр linger поддерживает работу экземпляра systemd для этого пользователя без активной сессии; это также позволяет контейнерам автоматически запускаться после перезагрузки системы.

Почему владельцем файлов в моем томе является UID 100999?

Rootless Podman отображает UID 0 контейнера на вашего пользователя хоста, а UID 1 и выше — на ваш диапазон subuid. Если диапазон начинается с 100000, то UID 1000 контейнера превращается в 100999 на хосте. Исправьте это изнутри пространства имен с помощью podman unshare chown 1000:1000 /path/to/data, используйте флаг :U при первом запуске или примените --userns=keep-id, чтобы UID контейнера совпадали с вашими собственными.

Могу ли я продолжать использовать docker-compose.yml с Podman?

Да, двумя способами. podman-compose считывает файл и управляет CLI Podman напрямую. Либо включите сокет совместимости с помощью systemctl --user enable --now podman.socket, установите DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock и запускайте настоящий docker compose, обращаясь к нему. Возможны сложности с network_mode: host, со службами, монтирующими сокет Docker, и с restart: always, для которого требуется юнит quadlet и включенный linger, чтобы он сохранялся после перезагрузки.

Действительно ли rootless делает контейнеры более безопасными?

Это устраняет один конкретный риск: процесс, вышедший из rootless-контейнера, обладает правами вашего непривилегированного пользователя, а не правами root. Это полезно, и именно поэтому у группы docker, эквивалентной root, нет аналога в rootless Podman. Это не защищает от уязвимостей ядра и не защищает файлы, доступные вашему пользователю, поэтому продолжайте применять остальные меры защиты, как на любом другом сервере.