SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-28

Podman чи Docker на VPS: різниця на практиці

Podman не має daemon і працює rootless за замовчуванням. Дізнайтеся, що це змінює для compose-файлів, quadlets, портів і власників volume на VPS.

Що насправді відрізняється між Podman і Docker

Podman і Docker запускають однакові OCI (open container initiative) images на VPS, тому вибір не залежить від того, яке програмне забезпечення можна запустити. Відмінність полягає в моделі процесів. Docker запускає root daemon, який керує всіма контейнерами, а команда docker є невеликим клієнтом, що просить цей daemon виконати потрібну операцію. У Podman немає daemon: podman run запускає контейнер як дочірній процес того, хто його викликав, від імені вашого непривілейованого користувача.

Усе інше випливає з цього факту. Автозапуск стає завданням systemd, а не daemon. Власник volume проходить через user namespace, тому власник, якого ви бачите на хості за допомогою ls -l, не є власником, якого бачить контейнер. Порти з номерами нижче 1024 не вдається прив’язати, доки ви не зміните параметр ядра. CLI (command line interface) docker продовжує працювати через wrapper, але лише доти, доки певній операції не знадобиться Docker socket.

Немає daemon: що насправді запускається під час запуску контейнера

На Docker host команда pstree -a показує dockerd від root, containerd поруч із ним і по одному containerd-shim-runc-v2 для кожного запущеного контейнера. Ваш застосунок є дочірнім процесом цього shim, а shim — дочірнім процесом PID 1. Ніщо не пов’язує контейнер із shell, з якого його запустили. Якщо зупинити daemon, ви втратите площину керування всіма контейнерами на цьому host. Якщо параметр live-restore вимкнено, systemctl restart docker також перезапустить ваші контейнери.

У Podman немає еквівалентного процесу. Запустіть контейнер — і отримаєте один процес conmon (container monitor), який утримує головний процес контейнера. Власником цього процесу є користувач, що виконав команду.

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 не зупиняє вже запущені контейнери. Збій monitor одного контейнера не впливає на інші.

Відсутність daemon також має наслідки. Після перезавантаження ніхто не запускає ваші контейнери. --restart=always у Docker — це гарантія, яку daemon виконує під час завантаження. У Podman її замінює systemd. Саме для цього призначений розділ про quadlet нижче.

Socket — інша частина цієї моделі. /var/run/docker.sock — це endpoint API (application programming interface), власником якого є root. Будь-який процес, що може записувати до нього, здатен запустити привілейований контейнер із монтуванням файлової системи host. Додавання користувача до групи docker надає цьому користувачу root-доступ обхідним шляхом. Про це варто прочитати разом із розділом надання кожному службовому обліковому запису лише потрібного доступу. Podman не відкриває socket, якщо ви не попросите про це. Отриманий socket належить одному користувачу в /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 ID. Без них rootless-контейнери не запускаються. podman info має вивести rootless: true.

Ubuntu 24.04 постачається з Podman 4.9, а Debian 13 — з Podman 5.x; ці версії перевірено в August 2026. Різниця важлива, оскільки quadlet-файли потребують версії 4.4 або новішої, а .pod quadlet-файли — версії 5.0. Виконайте podman --version, перш ніж копіювати приклад з upstream-документації.

Кожному rootless-користувачеві потрібен діапазон subordinate ID:

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.

Призначте діапазон, а потім скиньте storage цього користувача, щоб застосувати нове зіставлення:

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

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

Що насправді дають rootless-контейнери на орендованому сервері

Rootless-контейнер працює всередині user namespace — функції ядра, яка надає процесу власну ізольовану карту ідентифікаторів користувачів. Усередині namespace суперкористувачем контейнера є UID (ідентифікатор користувача) 0. Зовні, на вашому VPS, цей самий процес працює від імені вашого звичайного користувача для входу в систему. root у контейнері не є root на хості.

Це і є реальний масштаб переваги. Образ, який наполягає на запуску від імені root, вебзастосунок із вразливістю віддаленого виконання коду або escape-атака, що залежить від UID 0 за межами namespace, у всіх цих випадках отримують права вашого непривілейованого користувача, а не права всієї машини. Rootless не захищає від вразливостей ядра. Він також не захищає ваші власні файли, оскільки процес після escape працює від вашого імені й може читати все, що доступне вам. Не менш важливою за зіставлення UID є ізоляція самого unit. Це простіше побачити на прикладі FreeBSD jail, яка охоплює весь userland, що ви адмініструєте як невелику машину, а не пошарового образу, завантаженого з registry.

Docker також може працювати в rootless-режимі. dockerd-rootless-setuptool.sh install налаштовує daemon для окремого користувача, і це добре працює. Різниця полягає в напрямку, який задає типове налаштування. У Podman rootless працює без додаткового запиту, тому першою проблемою для вас стане контейнер, який не може прив’язати порт 80, а не сервіс, що непомітно працював від імені root протягом 2 років.

Чому файли у volume належать UID 100999?

Причина — той самий user namespace. UID 0 контейнера відповідає UID вашого хоста. UID 1 контейнера відповідає першому ідентифікатору у вашому діапазоні subuid, а далі значення збільшуються. Якщо діапазон починається з 100000, UID 1000 контейнера відповідає UID 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 також не допоможе, оскільки ваш непривілейований користувач не може змінювати власника файлів за межами namespace.

Є чотири способи вирішити проблему:

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

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

Ще два зауваження щодо mount. Прапори :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, яким володіє процес зі звичайними правами, тому до нього застосовуються вхідні правила вашого firewall. Docker публікує порти, додаючи правила NAT (network address translation) і власні правила дозволу пересилання. Саме тому опублікований порт Docker ігнорує правило ufw, яке, як ви думали, його блокує. Rootful Podman використовує подібний механізм і має ту саму проблему. Rootless-режим — ні.

Чи працюють мої файли Docker Compose у Podman?

Переважно так, є два варіанти. Перший — podman-compose, окрема реалізація, яка читає той самий файл і керує Podman CLI:

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 і використовуйте одну абстракцію замість двох.

Поди: концепція, для якої Docker не має відповіді

Под — це група контейнерів, які спільно використовують один мережевий простір імен. 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 із трьома контейнерами, включно з infra-контейнером. Тепер web-контейнер отримує доступ до Redis через 127.0.0.1:6379, а не через app-cache:6379. Із спільного простору імен випливають два правила: публікуйте порти на pod, а не на його учаснику, і не дозволяйте двом учасникам прослуховувати один і той самий порт.

Це модель Kubernetes, і Podman активно її підтримує. podman kube generate app > app.yaml створює маніфест Kubernetes на основі запущених компонентів (у старіших пакетах команда має назву podman generate kube), а podman kube play app.yaml відтворює його на іншому хості. Quadlet має тип unit .kube, який запускає такий файл як службу systemd. Це принципово інший спосіб групувати служби. Саме тому варто обрати Podman, якщо Kubernetes може знадобитися вам у майбутньому.

Автозапуск без daemon: quadlet units

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

~/.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:

[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. Згенеровані unit-файли не можна увімкнути, і systemd повертає Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated.. Саме секція [Install] запускає контейнер під час завантаження системи, а daemon-reload повторно генерує unit-файл після редагування цього файлу.

Тепер параметр, який помилково налаштовують майже всі:

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

Очікуйте Linger=yes. Без linger systemd завершує весь сеанс користувача, коли закривається останнє SSH-з’єднання. Разом із ним зупиняються всі rootless-контейнери, і під час наступного завантаження жоден із них не запускається. Якщо контейнери зникають після виходу з облікового запису, причина завжди в цьому.

Оскільки контейнер є головним процесом звичайного service unit, на нього безпосередньо поширюються засоби керування systemd. MemoryMax= і CPUQuota= у секції [Service] працюють так само, як для будь-якого іншого сервісу, яким ви керуєте через systemd. Для цього потрібен cgroup v2 (control group version 2), який Ubuntu використовує за замовчуванням із версії 22.04. Перевірте це за допомогою podman info | grep -i cgroup.

Для оновлень передбачено відповідний механізм. AutoUpdate=registry разом із systemctl --user enable --now podman-auto-update.timer перевіряє registry на наявність новішого image з тим самим tag, перезапускає unit і повертається до попереднього image, якщо новий контейнер не запускається. Спочатку виконайте 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 не має еквівалента, тому стек Swarm неможливо запустити в Podman. Інструментам, які працюють із Docker socket, потрібно експортувати Podman socket, але деякі з них усе одно виявляють різницю. Docker provider у Traefik працює, якщо вказати йому /run/user/<uid>/podman/podman.sock, а для Watchtower еквівалента немає, оскільки цю функцію виконує podman auto-update. Сховище є окремим, тому Podman не бачить образи, які ви вже завантажили через Docker, а podman images на зайнятому Docker host спочатку порожній.

Поетапна міграція запущеного стека

  1. Створіть або виберіть непривілейованого користувача, від імені якого працюватимуть контейнери, і переконайтеся, що для нього визначено діапазон у /etc/subuid.
  2. Повторно завантажте все, що походить із registry, використовуючи повністю кваліфіковані імена. 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 за reverse proxy або задайте 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 і його rebuild-дистрибутиви постачають Podman як підтримуваний рушій, тому в таких системах Podman створює менше несподіванок. Якщо на одному з таких хостів вам все одно потрібен Docker, шлях через dnf на Rocky Linux і AlmaLinux починається з видалення обгортки podman-docker, яка вже володіє командою docker. Якщо всі інші компоненти ви вже контролюєте через unit-файли systemd, quadlets сприйматимуться як відсутня частина системи, а не як новий інструмент, якого потрібно навчатися.

Варто згадати і проміжний варіант. Rootful Podman працює подібно до Docker, зберігає команду docker через обгортку та водночас усуває потребу в daemon, що постійно працює. Водночас він відмовляється від rootless-режиму — саме він змінює вашу модель безпеки, тому сприймайте rootful Podman як проміжний етап.

Якщо ви ще створюєте свій перший container host, шлях налаштування та hardening Docker на чистому VPS буде коротшим, і ці знання не будуть марними. Images і volumes є однаковими об’єктами в обох рушіях, тому подальший перехід змінить спосіб контролю сервісів і майже нічого більше.

FAQ

Чи є Podman повною заміною Docker?

Для команд, які ви вводите, майже так. Встановлення podman-docker додає оболонку /usr/bin/docker, а run, ps, build, logs і exec працюють так само. Це не заміна daemon. Swarm не має еквівалента. Інструменти, які підключаються до /var/run/docker.sock, потрібно спрямувати до socket Podman для відповідного користувача. Образи, завантажені Docker, залишаються невидимими для Podman, оскільки ці програми використовують окремі сховища.

Чому мої rootless-контейнери Podman зупиняються після виходу із SSH?

Тому що systemd зупиняє сеанс користувача і всі пов’язані з ним user service, коли завершується останній вхід. Виконайте sudo loginctl enable-linger <user>, потім переконайтеся, що loginctl show-user <user> --property=Linger виводить Linger=yes. Linger підтримує екземпляр systemd цього користувача запущеним без активного сеансу. Саме це також дає змогу контейнерам запускатися після перезавантаження.

Чому файли у моєму volume належать UID 100999?

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

Чи можна й надалі використовувати docker-compose.yml з Podman?

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

Чи справді rootless робить контейнери безпечнішими?

Він усуває один конкретний ризик: процес, який виходить із rootless-контейнера, отримує права непривілейованого користувача, а не root. Це важливо. Саме тому група docker, еквівалентна root, не має відповідника в rootless Podman. Rootless не усуває вразливості ядра і не захищає файли, які може читати ваш користувач. Тому продовжуйте застосовувати інші заходи hardening, які потрібні для будь-якого сервера.