Podman Compose: что это, как устроено, зачем нужно

Как устроен Podman Compose и чем он отличается от Docker Compose

Тирекс
Тирекс Самый зубастый автор
10 апреля 2026

В Podman аналогично есть Podman Compose — подкоманда, которая запускает внешний провайдер. В качестве такого провайдера может выступать Docker Compose / docker‑compose. Вот во всем этом сегодня и разберемся.

Изображение записи

Если у вас есть конфигурационный файл docker‑compose.yml, то в большинстве случаев его можно запустить через Podman без переписывания. Так вы, с одной стороны, сохраните привычный воркфлоу, а с другой — получите преимущества rootless‑контейнеров. В Docker же для запуска compose-файлов раньше использовалась внешняя утилита Docker Compose, но спустя время Docker внедрил возможность запускать такие файлы нативно через Docker Compose plugin.

Да, старый способ запуска все еще актуален. Его можно встретить на легаси-проектах, но лучше все-таки использовать современные методы и не тащить за собой старье.

  • Docker Compose (v2) = основной и современный стандарт.
  • docker-compose = легаси.
  • Podman Compose = официальный путь в Podman.
  • podman-compose = сторонний костыль с ограничениями.

Автор статьи — Роман Шубин, CTO и автор Telegram-канала Bash Days.

Podman Compose

Проверяем, что Podman Compose есть в системе:

Проверка версии установленного плагина Docker Compose (версия v5.0.2) через команду «podman compose -v».

Видим, что по умолчанию используется провайдер Docker’а. Podman будет проксировать команды из compose-файла в docker-compose. Плюсы, конечно, есть, но это не чистый Podman. Все же хочется чего-то настоящего. Давайте это исправим.

Устанавливаем:


      apt install podman-compose
 yum install podman-compose

Проверяем:

Вывод команды «podman-compose -v», показывающий версии установленного инструмента podman-compose (1.5.0) и самого Podman (5.7.1).

Отлично, теперь выглядит хорошо, без варнингов и Docker-прокладок. Podman будет использовать API напрямую. И это отдельный инструмент, а не тот, что вызывается через Podman Compose.

А что лучше использовать? Если хочется максимальной совместимости, то Podman Compose, а если нужен чистый Podman, то podman-compose. Все просто!

Чистый podman-compose

Хорошо, разобрались. Дальше будем рассматривать именно нативную версию compose. Берем пример compose.yaml.


      services:
   db:
 	image: postgres:16
 	environment:
   	POSTGRES_PASSWORD: example
 	volumes:
   	- db_data:/var/lib/postgresql/data

   app:
 	image: nginx:alpine
 	ports:
   	- "8080:80"
 	depends_on:
   	- db

 volumes:
   db_data:

Запускаем:


      podman-compose up -d

Кто не знал, файлы можно называть: compose.yaml, docker-compose.yml, container-compose.yml. Они будут автоматически подтягиваться, без указания каких-либо ключей в командной строке.

Проверяем:

Результат команды «podman ps», отображающий список запущенных контейнеров с образами postgres:16 и nginx:alpine, их ID и именами.

Что и следовало ожидать. Инструкции из файла были прочитаны и выполнены. По итогу запущено два контейнера нативными средствами Podman Compose.

Тонкости Podman Compose

Теперь самое интересное. Если выполнить podman pod ps, мы увидим:

Вывод команды podman pod ps, показывающий статус пода pod_dev. Под находится в состоянии Running, содержит 2 контейнера и был создан несколько секунд назад.
Вывод команды «podman ps --pod», показывающий контейнеры dev_db_1 и dev_app_1, объединенные в один под с общим POD ID.

Podman Compose автоматически создал pod и запустил в нем два контейнера, которые мы описали в compose-файле. Неожиданно и приятно. Если вы читали предыдущие мои статьи, то знаете, что контейнеры теперь находятся в общей сети, видят друг друга и могут общаться между собой по имени.

Podman Compose может создавать pod автоматически, но это не всегда очевидно, потому что podman ps по умолчанию их не показывает. Поэтому не забывайте, что существует команда podman pod ps.

Чтобы посмотреть логи в pod’е, можно воспользоваться командой:


      podman pod logs 2f7b3ae7ee7e
Логи пода (podman pod logs), содержащие записи запуска PostgreSQL 16.13 и Nginx 1.29.6, а также сообщение о готовности БД принимать соединения.

Она покажет все доступные логи со всех контейнеров которые крутятся в pod’е.

Запускаем проверки, заходим в контейнер:


      podman exec -it dev_app_1 sh

Пробуем подключиться к базе:


      curl db:5432
Выполнение команды «curl db:5432» внутри контейнера dev_app_1, возвращающее ошибку «Empty reply from server» вместо успешного соединения.

Да, все ок, подключиться удалось. А теперь давайте так:

Скриншот терминала с ошибкой «curl: (7) Failed to connect to localhost port 5432», демонстрирующий невозможность подключения к базе данных через localhost в обычном контейнере.

Опа! На localhost оно не хочет цепляться. Хотя контейнеры находятся в одном pod’е. Это что? Баг или фича? Попробуем разобраться. Заходим в контейнер с базой:


      ss -ltnp | grep 5432
Вывод команды «ss -ltnp | grep 5432» в терминале, показывающий, что порт 5432 прослушивается на всех интерфейсах (0.0.0.0 и [::]) внутри контейнера.

Ага, слушает нормально и принимает запросы на всех интерфейсах. Теперь посмотрим:


      podman inspect dev_app_1 | grep -i pod
 podman inspect dev_db_1 | grep -i pod
Скриншот терминала с выводом команды podman inspect для контейнеров dev_app_1 и dev_db_1. В JSON-структуре подсвечены одинаковые идентификаторы пода (Pod ID), что подтверждает объединение контейнеров в единое пространство имен через podman-compose.

Контейнеры работают в одном pod’е, об этом говорит строка "Pod": "130a8d46f10e003348b49e5c093a9c3e78d44416b59aedb840e95701b360dc08".

Поведение очень похоже на rootless-режим и такое ощущение, что Podman физически не объединяет loopback, если контейнеры запущены через compose-файл.

Попробуем запустить то же самое, но через команды:


      podman pod create --name test-pod
 podman run -d --pod test-pod --name db-postgres -e POSTGRES_PASSWORD=example postgres:16
 podman run -it --pod test-pod --name app-nginx alpine:latest sh
Процесс установки утилиты curl через «apk add» и последующая попытка подключения к localhost:5432, завершившаяся ошибкой «Empty reply from server» внутри пода.

Вот это поворот! А напрямую работает. Интересно… Может, это нативный Podman Compose так себя ведет? Проверим через docker-провайдер.

Комплексный скриншот терминала, демонстрирующий полный цикл: запуск podman compose up -d, проверку списка контейнеров и подов, а также неудачные попытки подключения через curl к db:5432 и localhost:5432 (ошибки «Empty reply» и «Failed to connect»).

Нет, так тоже не сработало. Так почему один и тот же pod ведет себя по-разному? А потому что это не один и тот же pod с точки зрения сети. Вот вы запускаете команды:


      podman pod create --name test-pod
 podman run -d --pod test-pod --name db-postgres -e POSTGRES_PASSWORD=example postgres:16

Podman создает infra container, создает единый network namespace, все контейнеры делят этот namespace, включая loopback 127.0.0.1. Поэтому localhost работает!

А через podman-compose сетевой стек создается по-другому. Сначала создается bridge, потом к этому bridge подключаются контейнеры. А pod лишь логически их объединяет между собой.

С одной стороны это не страшно, ведь обращение по имени сервиса работает. Что еще надо? Да особо ничего, с этим обычно трудностей не возникает. Но про этот нюанс Podman Compose вам стоит знать.

Podman-compose — ориентирован на docker-compose spec, а в Docker нет pod’ов, есть только сеть. Это не баг, а фича дизайн. Поэтому pod создается «сверху», но сеть остается docker-like.

Это все не очень гибко, конечно. Но если нужна гибкость, то добро пожаловать в Kubernetes, где все это работает «из коробки». Правда, там начнутся совсем другие проблемы, так что это палка о двух концах.

Так что в Podman Compose всегда используйте имена сервисов db, redis и т. п. вместо localhost. Будет легче ориентироваться по именам, чем просто указывать порты и вести их на localhost.

Какие еще возможности есть

Да все то же самое, что и в Docker Compose. В podman-compose отличия лишь в rootless-режиме, отсутствии отдельного демона и имеющейся возможности использовать юниты через Quadlet.

Кстати я бы весь этот podman-compose смело заменил на Quadlet — с ним меньше мороки и он более очевидный в действиях. Я о нем тоже уже рассказывал. Главное его преимущество — гибкость в настройке. Ну и декларативность. Для продакшена это мастхэв.

Да, есть еще интересная фича. Генерация манифестов для Kubernetes:


      podman generate kube 5bf31e040908

Подставляете ID своего pod’а, который запустили через podman-compose, и на выходе получаете готовый манифест:

Сгенерированный YAML-файл манифеста Kubernetes (apiVersion: v1, kind: Pod) для пода с контейнерами postgres и nginx, созданный командой «podman generate kube».

По необходимости правите нужное и деплоите в продакшен. Отличное решение для миграции. Но это не полноценный куберовский YAML, в нем нет Deployment, StatefulSet. Лишь только pod и иногда Service.

Итог

Так можно ли назвать Podman Compose альтернативой Docker Compose? Скорее нет. Это все же инструмент с другим подходом. Он дает доступ к возможностям самого Podman: rootless-запуску, интеграции с systemd, работе с pod’ами и генерации Kubernetes-манифестов.

Но вместе с этим приходит и другая модель поведения, особенно в networking и управлении контейнерами. Если вам нужен привычный docker-like опыт — Podman Compose справится.

Главное правило — не переносить привычки из Docker в Podman бездумно, а немного понимать, что вы делаете и как оно работает под капотом.