ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

Docker Compose без Docker: rootless Podman и systemd на VDS

Разберёмся, как жить без Docker на продовом VDS: установим rootless Podman, запустим проекты с docker-compose.yml и оформим всё через systemd. Пошагово пройдём установку, миграцию в podman-compose и Quadlet, подключим автозапуск и разберём типичные грабли с сетью и volume.
Docker Compose без Docker: rootless Podman и systemd на VDS

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.

Схема интеграции rootless Podman контейнеров с systemd на VDS

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, память и т.п.).

Пример unit-файлов Quadlet для веб-приложения и базы данных

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:

  1. Проверить версии ядра и дистрибутива, установить Podman и утилиты для rootless (uidmap, slirp4netns).
  2. Создать отдельного пользователя podman, выдать ему диапазоны UID/GID, включить linger.
  3. Под этим пользователем оттестировать запуск простого контейнера в rootless-режиме.
  4. Скопировать один проект с docker-compose.yml в домашний каталог podman, попробовать запустить через podman-compose up -d.
  5. Разрулить несовместимости: заменить network_mode: host на порты, избавиться от привилегированных режимов, проверить volume и права на них.
  6. Добавить пользовательский unit systemd для управления podman-compose (быстрый старт) или постепенно переписать проект на Quadlet (.container/.volume/.network).
  7. Настроить фронтовой reverse proxy на хосте (nginx/haproxy), который слушает 80/443 и отдаёт трафик в rootless-контейнеры.
  8. Добавить мониторинг: логов через 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 с предсказуемыми ресурсами, а затем уже разворачивать на нём нужный стек.

Поделиться статьей

Вам будет интересно

Bash one-liners с jq, curl и xargs: практические рецепты для админов OpenAI Статья написана AI (GPT 5)

Bash one-liners с jq, curl и xargs: практические рецепты для админов

Разбираем практические Bash one-liners с jq, curl и xargs: как быстро выдёргивать данные из JSON, пачками дергать HTTP API, провер ...
Nginx + бесплатный cookie-free CDN как origin: пошаговая схема и тонкая настройка OpenAI Статья написана AI (GPT 5)

Nginx + бесплатный cookie-free CDN как origin: пошаговая схема и тонкая настройка

Разбираем, как подключить сайт на Nginx к бесплатному CDN и использовать сервер как origin без сюрпризов. Пошагово настраиваем coo ...
HTTP API-шлюз на Nginx: rate limit, quota и версионирование OpenAI Статья написана AI (GPT 5)

HTTP API-шлюз на Nginx: rate limit, quota и версионирование

Разбираем, как построить лёгкий HTTP API gateway на Nginx без тяжёлых сервис-мешей: маршрутизация по версиям API, ограничение RPS ...