SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-13

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

Podman работает без демона и root-прав, что меняет управление томами, портами и автозапуск через systemd. Узнайте, как это влияет на работу compose-файлов и прав доступа на VPS.

В чем на самом деле разница между Podman и Docker

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

Все остальное вытекает из этого факта. Автозапуск становится задачей systemd, а не демона. Владение томами передается через пространство имен пользователя (user namespace), поэтому владелец, которого вы видите с помощью ls -l на хосте, не является владельцем, которого видит контейнер. Порты ниже 1024 отказываются привязываться, пока вы не измените настройку ядра. Интерфейс командной строки (CLI) 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, веб-приложение с уязвимостью удаленного выполнения кода, выход из контейнера (escape), зависящий от наличия UID 0 снаружи — все это в итоге ограничивается правами вашего непривилегированного пользователя, а не правами всей системы. Rootless не защищает от ошибок в ядре и не защищает ваши личные файлы, так как сбежавший процесс выполняется от вашего имени и может читать все, что доступно вам.

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-опубликованный порт — это обычный listening socket, принадлежащий обычному процессу, поэтому к нему применяются правила входящего трафика вашего межсетевого экрана. 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 ведет себя иначе в пространстве имен пользователя. 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 (control group версии 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 всё ещё существует, но она считается устаревшей, поэтому для всех новых задач пишите quadlets.

Где работает псевдоним 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, требуется экспортированный сокет Podman, и некоторые из них всё равно замечают разницу; Docker-провайдер 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 — это путь с наименьшим количеством сюрпризов. Если вы уже управляете всем остальным через 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, для которого потребуется unit-файл quadlet и включенный режим linger для автозапуска после перезагрузки.

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

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