SSD Nodes Learn 🎉 VPS від $5.50/міс
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-13

Podman чи Docker на VPS: у чому реальна різниця

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

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

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

Усе інше випливає з цього факту. Автозапуск стає завданням systemd, а не демона. Власник тому проходить через 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, ви втратите площину керування для всіх контейнерів на цьому хості. Якщо типове налаштування 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 не зупиняє вже запущені процеси. Збій монітора одного контейнера не впливає на інші контейнери.

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

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

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

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 — функції ядра, яка надає процесу власне приватне зіставлення ідентифікаторів користувачів. Усередині namespace суперкористувач контейнера має UID (ідентифікатор користувача) 0. За межами namespace, на вашому VPS, цей самий процес працює від імені вашого звичайного користувача для входу в систему. root у контейнері не є root на хості.

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

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

Чому мої файли тома належать 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 у контейнері, тому нові файли належатимуть вам.
  • Іменований том, наприклад -v appdata:/data, усуває цю проблему, оскільки Podman створює його у вашому власному сховищі з правильним власником.

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

Ще два зауваження щодо монтувань. Прапорці :z і :Z, які використовуються у прикладах для Fedora та RHEL, є параметрами перемаркування SELinux, а Ubuntu використовує AppArmor, тому в Ubuntu вони нічого не роблять. 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, а перенаправлені з’єднання надходять із переписаною адресою джерела. Тому в журналі доступу кожен відвідувач записується як 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 і використовуйте одну абстракцію замість двох.

Pods: концепція, якої Docker не підтримує

Pod — це група контейнерів, які спільно використовують один мережевий простір імен. Podman запускає невеликий контейнер infra, щоб утримувати цей простір імен відкритим, а його учасники взаємодіють один з одним через 127.0.0.1 без user-defined мережі та 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 manifest на основі запущених ресурсів (у старіших пакетах він має назву podman generate kube), а podman kube play app.yaml відтворює його на іншому хості. Quadlet має тип unit .kube, який запускає такий файл як сервіс systemd. Це принципово інший спосіб групування сервісів і найвагоміша причина обрати Podman, якщо Kubernetes може знадобитися вам у майбутньому.

Автоматичний запуск без daemon: quadlet units

Quadlet — це systemd generator. Він перетворює короткий файл з описом контейнера на справжній systemd service під час завантаження системи. Файли розміщують у ~/.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:

[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. Згенеровані units не можна увімкнути, і 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.

Де працює alias 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 немає куди розгорнути. Інструментам, які працюють із 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. Повторно завантажте з реєстру все, що звідти отримували, використовуючи повні імена. 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 і його rebuilds постачають Podman як підтримуваний рушій, тому в таких системах Podman є варіантом із меншою кількістю несподіванок. Якщо все інше ви вже контролюєте за допомогою systemd units, quadlets сприйматимуться як відсутня частина системи, що нарешті з’явилася, а не як новий інструмент, якого треба навчатися.

Варто згадати один проміжний варіант. Rootful Podman багато в чому працює як Docker, зберігає команду docker через wrapper і водночас усуває потребу в постійно запущеному daemon. Він також відмовляється від rootless-режиму, а саме цей режим змінює вашу security position, тому розглядайте 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, потрібно спрямувати до per-user Podman socket, а образи, завантажені Docker, залишаються невидимими для Podman, оскільки ці програми використовують окремі сховища.

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

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

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

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

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

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

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

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