Docker по-прежнему де-факто стандарт для контейнеров в веб-разработке, но всё чаще в проде на VDS админы смотрят в сторону Podman. Причины простые: безопасность (rootless), нативная интеграция с systemd и отсутствие тяжёлого демона. Проблема в том, что почти любой готовый пример в интернете — под docker и docker compose. В этой статье разберём, как аккуратно жить в мире Podman и systemd, не переписывая всё с нуля.
Я буду опираться на типичный сценарий: у вас есть VDS с Linux (чаще всего Debian/Ubuntu/Rocky/Alma), есть или был Docker, есть несколько проектов с docker-compose.yml. Хотите минимум боли, rootless-контейнеры пользователем без прав root и удобный автозапуск через systemd.
Podman vs Docker: что меняется на VDS
Podman позиционируется как drop-in replacement для Docker CLI: многие команды просто совпадают по синтаксису. Но архитектура другая: нет постоянного демона (типа dockerd), каждый запуск — обычный процесс, который может работать в rootless-режиме от имени вашего пользователя.
Ключевые отличия, которые важно понимать перед миграцией:
- Rootless по умолчанию: Podman можно запускать как обычный пользователь. Это серьёзно уменьшает последствия компрометации контейнера.
- Интеграция с systemd: Podman умеет сам генерировать unit-файлы systemd и даже работать через systemd socket-activation.
- Совместимость с Docker CLI: команды вида
podman run,podman ps,podman logsпрактически идентичны Docker. Но есть нюансы по volume, network и rootless-ограничениям. - Compose-история: официальный docker-compose — Python/Go-утилита от Docker. Для Podman есть несколько вариантов:
podman-compose,podman kubeи современный подход черезquadlet(unit-файлы systemd).
С точки зрения эксплуатации на VDS нас больше всего интересует связка rootless Podman + systemd: контейнеры работают от отдельного пользователя, подхватываются автозапуском и управляются через знакомый systemctl.
Установка Podman и базовая готовность rootless
Дальше буду опираться на свежие дистрибутивы Debian/Ubuntu или RHEL-совместимые (Rocky, Alma). На старых придётся подключать backports или официальные репозитории.
Установка Podman
Примеры для Debian/Ubuntu:
sudo apt update
sudo apt install podman uidmap slirp4netns
Для RHEL-подобных:
sudo dnf install podman uidmap slirp4netns
Проверяем, что Podman установлен и работает хотя бы в root-режиме:
podman info
Если команда от обычного пользователя завершилась успешно — уже почти всё готово для rootless.
Настройка rootless-пользователя
Лучше всего завести отдельного системного пользователя под контейнеры, а не запускать всё от своего обычного SSH-пользователя, у которого есть доступ к репозиториям и ключам.
sudo useradd -m -s /bin/bash podman
sudo passwd podman
Заходим под этим пользователем:
sudo su - podman
Проверяем rootless-режим:
podman info --log-level=debug | grep -i rootless
Если видим, что режим rootless активен, уже можно запускать тестовый контейнер:
podman run --rm quay.io/podman/hello
Если всё прошло гладко, значит на VDS нормально настроены /etc/subuid и /etc/subgid. Если нет — проверьте, что пользователю выделены диапазоны UID/GID:
grep podman /etc/subuid
grep podman /etc/subgid
Если строк нет, добавьте, например:
sudo sh -c 'echo "podman:100000:65536" >> /etc/subuid'
sudo sh -c 'echo "podman:100000:65536" >> /etc/subgid'
После этого снова зайдите под пользователем podman и повторите тестовый запуск.
Что делать с существующими docker-compose.yml
Большинство живых проектов используют docker compose или старый docker-compose. Для миграции под Podman есть два основных пути:
- podman-compose — утилита, которая читает
docker-compose.ymlи запускает сервисы через Podman. - Quadlet + systemd units — переписать (или сгенерировать) конфигурацию в .container/.volume/.network unit-файлы для systemd.
Первый путь проще, особенно если вам нужно быстро запустить готовый проект на VDS без переезда на Kubernetes или переписывания манифестов. Второй — надёжнее и лучше интегрируется с systemd, но потребует чуть больше работы.
Вариант 1: podman-compose как drop-in замена docker-compose
podman-compose обычно ставится из пакета дистрибутива или через pip. Пример для Debian/Ubuntu, где нет свежего пакета:
sudo apt install python3-pip
pip3 install --user podman-compose
Убедитесь, что ~/.local/bin в PATH пользователя podman:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Проверяем версию:
podman-compose --version
Дальше можно зайти в директорию проекта, где лежит docker-compose.yml, и попробовать:
cd ~/projects/myapp
podman-compose up -d
Часто это «просто работает». Но есть важные нюансы, которые в docker-compose-листингах обычно не учитывают.
Типичные несовместимости docker compose и podman-compose
Основные грабли при переезде:
- Network mode. Rootless-контейнеры не могут использовать
network_mode: host. Если вашdocker-compose.ymlна это опирается, придётся либо запускать Podman с root, либо пробросить порты через обычныеportsи слушать на нестандартных портах. - Привилегии. Параметры вроде
privileged: true,cap_add, монтирование системных сокетов (/var/run/docker.sock) в rootless-режиме либо не работают, либо работают сильно ограниченно. - Volume с правами доступа. При rootless Podman владельцем файлов на хосте будет некто с UID из выделенного диапазона, а не «настоящий» root. Иногда это ломает приложение, если оно ожидает конкретный UID внутри контейнера.
- sysctl и ulimits. В
docker-compose.ymlих любят прописывать. В rootless-режиме большинство таких настроек недоступно.
Если вам нужен полный контроль над сетью, капабилити и sysctl, можно запустить Podman как root, но тогда вы частично теряете преимущество rootless-безопасности. Для большинства веб-приложений обычно хватает rootless-режима с корректным port mapping.
Если вы как раз пересобираете инфраструктуру под контейнеры, полезно заранее продумать архитектуру: где будут крутиться фронтенды, базы, бэкапы и CI-сборки. Для вдохновения посмотрите материалы про изоляцию контейнеров и типовые паттерны построения прод-окружений, например про усиленную изоляцию контейнеров через gVisor и Firecracker: как повысить безопасность контейнеров gVisor и Firecracker.
Подхватываем Podman контейнеры через systemd
Одно из главных преимуществ Podman по сравнению с Docker на VDS — глубокая интеграция с systemd. Вам не нужно держать отдельную «надстройку» над docker-compose: контейнеры можно оформить как обычные systemd-сервисы и управлять ими через systemctl.
Генерация unit-файла для одиночного контейнера
Для начала рассмотрим простой сценарий без compose: одиночный контейнер, который должен стартовать вместе с системой.
Запускаем контейнер как обычно:
podman run -d --name my-nginx -p 8080:80 nginx:stable
Теперь генерируем unit-файл systemd для этого контейнера. Если речь об обычном rootless-пользователе, используем --user и --new, чтобы создать новый unit:
podman generate systemd --name my-nginx --new --files --restart-policy=always
Команда сгенерирует файл вида container-my-nginx.service в текущей директории. Его нужно переместить в пользовательские юниты:
mkdir -p ~/.config/systemd/user
mv container-my-nginx.service ~/.config/systemd/user/
Подгружаем конфигурацию и включаем автозапуск:
systemctl --user daemon-reload
systemctl --user enable --now container-my-nginx.service
Теперь контейнер будет жить под управлением systemd конкретного пользователя, перезапускаться при падении и стартовать после логина пользователя (или сразу при загрузке, если включить linger).
Включаем linger, чтобы user systemd работал без логина
По умолчанию user-systemd запускается только после входа пользователя в систему. Для rootless-сервисов на VDS это неудобно: мы хотим, чтобы контейнеры поднимались сразу при старте сервера. Для этого включается linger:
sudo loginctl enable-linger podman
После этого юзерские сервисы systemd --user для пользователя podman будут запускаться автоматически при старте системы, даже если никто не заходил по SSH.

Compose-проект под systemd: стратегии
С одиночным контейнером всё довольно просто. Но большинство продовых проектов на VDS — это связка из нескольких сервисов: веб-приложение, база, кеш, фоновые воркеры и т.п. Их обычно описывают в одном docker-compose.yml.
Подходов два:
- Управлять всем через podman-compose, а в systemd вынести один юнит, который запускает
podman-compose up. - Разложить проект на отдельные контейнеры, сгенерировать для каждого unit-файл через
podman generate systemdилиquadlet, связать их черезRequires/After.
Первый вариант проще, второй — чище и надёжнее, особенно по части рестартов и логики зависимостей.
Вариант A: systemd unit поверх podman-compose
Представим, что у вас есть директория /home/podman/projects/myapp с docker-compose.yml. Уже оттестировано, что podman-compose up -d работает. Хотим добавить автозапуск через systemd.
Создаём unit-файл для пользовательского systemd:
mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/myapp-compose.service
Пример содержимого:
[Unit]
Description=MyApp via podman-compose
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
WorkingDirectory=/home/podman/projects/myapp
ExecStart=/home/podman/.local/bin/podman-compose up -d
ExecStop=/home/podman/.local/bin/podman-compose down
RemainAfterExit=yes
TimeoutStartSec=300
TimeoutStopSec=300
[Install]
WantedBy=default.target
Активируем:
systemctl --user daemon-reload
systemctl --user enable --now myapp-compose.service
Теперь systemctl --user status myapp-compose.service покажет состояние, а journalctl --user -u myapp-compose.service — логи запуска и остановки.
Плюсы этого подхода:
- минимальные изменения в проекте — не нужно переписывать конфиг;
- простой деплой:
git pullиpodman-compose up -d(или перезапуск сервиса).
Минусы:
- systemd не знает о каждом контейнере по отдельности, не может перезапустить конкретный сервис при падении;
- часть ошибок остаётся внутри
podman-compose, а не в systemd-юнитах отдельных контейнеров.
Вариант B: Quadlet и нативные systemd units для Podman
Quadlet — это надстройка над systemd, которая позволяет описать Podman-контейнеры unit-файлами в формате, похожем на INI. Под капотом systemd сам генерирует нужные сервисы и управляет ими. Это более современный и контролируемый способ, чем podman-compose поверх systemd.
Пример: у нас есть простой стек из двух сервисов — web и db. В docker-compose.yml это могло бы выглядеть так (упрощённо):
version: "3.8"
services:
db:
image: postgres:15
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
web:
image: myorg/myapp:latest
ports:
- "8080:80"
environment:
DB_HOST: db
DB_USER: app
DB_PASSWORD: secret
DB_NAME: app
depends_on:
- db
volumes:
db-data:
Перепишем это в Quadlet. Для rootless-пользователя создаём каталог:
mkdir -p ~/.config/containers/systemd
Создадим volume-юнит для базы:
nano ~/.config/containers/systemd/db-data.volume
Пример содержимого:
[Volume]
Name=db-data
Driver=local
Создаём контейнер базы данных:
nano ~/.config/containers/systemd/db.container
[Unit]
Description=PostgreSQL for MyApp
After=network-online.target
Wants=network-online.target
[Container]
Image=postgres:15
Name=myapp-db
Env=POSTGRES_USER=app
Env=POSTGRES_PASSWORD=secret
Env=POSTGRES_DB=app
Volume=db-data:/var/lib/postgresql/data
[Service]
Restart=always
RestartSec=5
Теперь контейнер приложения:
nano ~/.config/containers/systemd/web.container
[Unit]
Description=MyApp web service
After=db.service
Requires=db.service
[Container]
Image=myorg/myapp:latest
Name=myapp-web
PublishPort=8080:80
Env=DB_HOST=myapp-db
Env=DB_USER=app
Env=DB_PASSWORD=secret
Env=DB_NAME=app
[Service]
Restart=always
RestartSec=5
После этого перезагружаем конфигурацию:
systemctl --user daemon-reload
И включаем нужные сервисы:
systemctl --user enable --now db.service
systemctl --user enable --now web.service
systemd сам подхватит, что web.service зависит от db.service, а Podman — что db.service использует volume db-data. В результате вы получаете:
- контейнеры, управляемые systemd как отдельные сервисы;
- автоматический рестарт, зависимости, понятные статусы и логи;
- возможность задавать лимиты ресурсов через systemd (CPU, память и т.п.).

rootless, порты и firewall: частые вопросы
Главный практический вопрос при rootless Podman на VDS: как слушать на «низких» портах (80/443) и дружить с firewall.
Порты ниже 1024 в rootless-режиме
Ограничение Linux: непривилегированный процесс не может слушать порт < 1024. Поэтому rootless Podman сам по себе не сможет поднять контейнер на 80 или 443. Есть варианты обхода:
- слушать контейнер на порту >= 1024 (например, 8080/8443), а на хосте сделать reverse proxy (nginx/haproxy) или port forwarding с root-правами;
- использовать
authbindилиsetcapдля биндинга на низкие порты, но это уже компромисс по безопасности; - для чистого варианта — rootful Podman только для фронтового прокси, а все приложения — rootless.
На практике на VDS обычно используют nginx на хосте, который слушает 80/443 и проксирует трафик в rootless-контейнер по 127.0.0.1:8080. Для TLS при этом удобно ставить обычный веб-сервер на хосте и привязывать к нему ваши SSL-сертификаты, а не тащить ключи внутрь контейнеров.
Firewall и rootless-сети Podman
Для rootless Podman по умолчанию используется user-space NAT (slirp4netns). С точки зрения хоста это обычные исходящие соединения, а входящие приходят на порт, который Podman пробросил с хоста в контейнер.
Если вы используете firewall (nftables, iptables, ufw), достаточно открыть соответствующие порты на хосте. Никаких специальных правил для внутренней сети Podman обычно не нужно, потому что она живёт в пространстве пользователя.
Логи, перезапуски и отладка
С Docker многие привыкают смотреть логи через docker logs. С Podman и systemd появляется ещё один слой — и это плюс: системные логи лежат в journalctl, а контейнерные можно читать и там, и через podman logs.
Где смотреть логи контейнеров
Если вы используете Quadlet или podman generate systemd, все stdout/stderr контейнера уходят в journald:
journalctl --user -u web.service -f
При необходимости можно настроить отдельный лог-драйвер для Podman (journald, k8s-file, json-file и т.п.). Посмотреть текущие настройки:
podman info | grep -i log
Отдельно можно читать логи контейнера:
podman ps
podman logs myapp-web
Автоматический рестарт и лимиты ресурсов через systemd
В systemd-юнитах (Quadlet и сгенерированных) можно задавать рестарт-политику и лимиты ресурсов. Примеры:
[Service]
Restart=always
RestartSec=5
# Ограничение по памяти и CPU для контейнера как для systemd-сервиса
MemoryMax=1G
CPUQuota=200%
Такие настройки особенно полезны на небольшом VDS, где легко словить OOM из-за неправильно сконфигурированного контейнера.
План миграции с Docker на Podman на VDS
Сведём всё к практическому пошаговому плану для реального VDS:
- Проверить версии ядра и дистрибутива, установить Podman и утилиты для rootless (
uidmap,slirp4netns). - Создать отдельного пользователя
podman, выдать ему диапазоны UID/GID, включить linger. - Под этим пользователем оттестировать запуск простого контейнера в rootless-режиме.
- Скопировать один проект с
docker-compose.ymlв домашний каталогpodman, попробовать запустить черезpodman-compose up -d. - Разрулить несовместимости: заменить
network_mode: hostна порты, избавиться от привилегированных режимов, проверить volume и права на них. - Добавить пользовательский unit systemd для управления
podman-compose(быстрый старт) или постепенно переписать проект на Quadlet (.container/.volume/.network). - Настроить фронтовой reverse proxy на хосте (nginx/haproxy), который слушает 80/443 и отдаёт трафик в rootless-контейнеры.
- Добавить мониторинг: логов через
journalctl, алерты по ресурсам, лимитыMemoryMax/CPUQuotaв systemd.
Отдельно продумайте резервное копирование: что вы будете бэкапить — только данные volume, дампы баз или целиком конфиг Quadlet и git-репозитории. Подходы к резервному копированию контейнерных сервисов мы детально разбирали в заметке про CI-бэкапы и sandbox: как безопасно восстанавливать окружения из CI-бэкапов.
Итоги
Связка Podman + systemd на VDS — это удобная и более безопасная альтернатива классическому Docker + docker-compose. Вы получаете:
- rootless-контейнеры, которые не требуют root-доступа для повседневной работы;
- нативный автозапуск и управление через systemd, без дополнительных демонов;
- возможность использовать уже существующие
docker-compose.ymlчерезpodman-composeкак переходное решение; - чистую, декларативную конфигурацию через Quadlet для долгоживущих продовых сервисов.
Если вы только планируете перенос или развёртывание контейнерной инфраструктуры на новом VDS, есть смысл сразу проектировать её под Podman и systemd: это упростит эксплуатацию, сократит поверхность атаки и даст больше гибкости по ресурсам и обновлениям. Для старта удобно использовать надёжный VDS с предсказуемыми ресурсами, а затем уже разворачивать на нём нужный стек.


