Docker годами приучал к мысли, что контейнеры обязаны жить вокруг одной привилегированной службы: ей отдают сокет, образы, сети и право решать всё подряд. Podman ломает привычную схему элегантно: команды те же, форматы файлов читаются, Dockerfiles собираются, а центральная служба с её сокетом просто исчезает из картинки. Честно говоря, редкий переезд с проверенного инструмента бывает оправдан, но когда ценой пары вечеров получаешь контейнеры без прав root, честный автозапуск через systemd и защиту от самого себя, игра стоит свеч. Ниже вся дорога: как Podman устроен внутри, что заработает сразу после алиаса, как жить без привилегий, чем заменить привычный compose и куда девать автозапуск при перезагрузке сервера.
Как Podman запускает контейнеры без центральной управляющей службы
В классической архитектуре Docker командная строка тонкая: она стучится в сокет, а вся работа идёт в привилегированной службе, которая запущена от root и правит бал от имени всех пользователей сразу. Уронить такую службу неприятно: контейнеры продолжат жить под контейнерным рантаймом, но команды перестанут слушаться. И главный сюрприз для новичков в том, что любой, кто попал в группу docker, фактически получил права суперпользователя, ведь через сокет можно смонтировать корень диска в контейнер и выйти наружу. Панели мониторинга, секреты, реестры внутри компании, всё это держится на одном доверии к сокету.
Podman устроен иначе, и в этом его соль. Центральной службы нет вообще: утилита при запуске контейнера сама делает fork и exec, создаёт процесс через монитор conmon, и контейнер остаётся обычным потомком в дереве процессов того, кто его запустил. Сокета с суперправами не существует, поэтому и заветной дверцы для повышения привилегий нет. У каждого пользователя своё дерево: podman ps показывает ровно то, что запустил этот человек, и ничью чужую работу он не видит. Стороной выходит и живучесть: нет службы, которую можно уронить целиком, есть отдельные процессы под присмотром systemd.
Установка Podman и алиас для привычных команд Docker
Ставится Podman пакетом. В штатных репозиториях Ubuntu 24.04 лежит сборка ветки 4.x, линия проекта на осень 2026 давно ушла в 6.x, так что жаждущие свежизны подключают пакеты из репозитория проекта containers:
sudo apt update
sudo apt install podman uidmap
podman --version
podman info
Совместимость команд задумана как главная фишка: run, ps, images, pull, push, build, inspect, logs, exec, volume, network и даже system prune узнаются без словаря. Чтобы пальцы не переучивались, в оболочке включают алиас:
echo "alias docker=podman" >> ~/.bashrc
source ~/.bashrc
docker ps
Команда docker ps теперь честно выполняет podman ps, и старые привычки продолжают работать. Чего в Podman нет из коробки, так это инфраструктуры плагинов и отдельного клиента с контекстами, к которой привыкли пользователи Docker Desktop. Практика переезда показывает: первые дни алиас спасает постоянно, а через неделю рука сама пишет podman.
Контейнеры без root через user namespaces и диапазоны subuid
Второе преимущество, ради которого затевают переезд, это rootless режим: контейнеры вообще без привилегий суперпользователя. Работает он на механизме ядра user namespaces: root внутри контейнера отображается в обычного пользователя снаружи, а номера владельцев файлов сдвигаются на выделенные диапазоны UID. Эти диапазоны описаны в двух файлах, и проверка занимает секунды:
cat /etc/subuid
cat /etc/subgid
Стандартный диапазон на пользователя в Ubuntu занимает шестьдесят с лишним тысяч идентификаторов, начиная с сотой тысячи: 100000-165535. Если строки для пользователя нет, её добавляет usermod с флагами добавления диапазонов, а пакет uidmap обеспечивает утилиты, через которые непривилегированный процесс получает право стартовать namespace с отображением. После этого контейнер, запущенный обычным пользователем, хранит образы и слои в домашнем каталоге, в ~/.local/share/containers/storage, и не трогает системные пути вообще.
Плата за изоляцию двойная, и обе половины решаемы. Файлы, созданные контейнером на подмонтированном каталоге, снаружи принадлежат сдвинутым номерам, и без отображения прав доступ к ним выглядит пугающе. Лечится утилитой podman unshare, которая выполняет команду внутри пространства пользователя, где сдвиг перестаёт мешать:
podman unshare ls -ln /var/www/site
podman unshare chown -R 0:0 /var/www/site
Нулевой номер внутри namespace и есть ваш пользователь снаружи, поэтому chown к нулю приводит права в порядок для контейнера. Вторая половина платы: контейнеры одного пользователя не видны ни root, ни соседям. Для сервера это скорее плюс, чем минус, но админам, привыкшим видеть всё сразу, придётся пересобрать привычки.
Сети в режиме без root и порты ниже 1024 через pasta
Сеть без привилегий раньше была главной болью rootless контейнеров, пока её не взял на себя pasta: крошечный прокси, который поднимается в пространстве пользователя и соединяет контейнер с внешним миром. В свежих ветках Podman pasta работает по умолчанию, в старых ту же роль играл slirp4netns, чьё имя до сих пор встречается в старых инструкциях. Внутри контейнера сеть выглядит как настоящая: свой адрес, свои порты, а в свежих версиях адрес хоста доступен по имени host.containers.internal, что удобно для связи приложения с базой на самой машине.
Проброс портов наружу выглядит ровно как раньше, флаг publish в команде запуска открывает нужный порт:
podman run -d --name web -p 8080:80 docker.io/library/nginx:stable
curl -I http://localhost:8080
Загвоздка одна: непривилегированному пользователю запрещено занимать порты ниже 1024, а вебу нужны 80 и 443. Лечится одной строчкой sysctl, опускающей границу для непривилегированных портов:
echo "net.ipv4.ip_unprivileged_port_start = 80" | sudo tee /etc/sysctl.d/99-rootless.conf
sudo sysctl --system
После перечитывания настроек контейнер спокойно публикует 443 порт от имени обычного пользователя. Сайт на одном хостинге, база в контейнере рядом, панель позади обратного прокси, всё это живёт без единого процесса от root. Для служб, которым нужен только внутренний обмен, хватает и собственной сети: podman network create принимает те же аргументы, что в Docker, а контейнеры внутри одной pod группы видят друг друга и без сети вовсе.
Запуск Compose проектов и замена привычному docker compose
Файлы compose на переезде терять не хочется, и тут есть два честных пути. Первый короче: у Podman есть обёртка podman compose, которая находит внешний провайдер и передаёт ему работу, провайдером служит либо podman-compose, либо знакомый docker-compose. Второй путь переиспользует настоящий плагин: docker compose умеет работать поверх сокета Podman, если сокет поднять и подсунуть ему адрес:
systemctl --user enable --now podman.socket
export DOCKER_HOST=unix:///run/user/1000/podman/podman.sock
docker compose up -d
Переменная DOCKER_HOST работает без обмана: плагин видит перед собой обычный сокет и разговаривает с Podman, как привык разговаривать с Docker. Большинство рядовых проектов с сетями, томами и зависимостями запускается с первого раза, экзотика вроде внешних плагинов томов может заупрямиться, и боевой стек перед переносом стоит прогнать на тестовой машине.
У Podman есть и собственная альтернатива группировке сервисов, называется pod. Команда podman pod create заводит группу, контейнеры добавляются в неё флагом pod, и все делят одно сетевое пространство: localhost одного контейнера видит соседа по портам, а значит приложению не нужны ни алиасы, ни лишниеpublish. Для пары из приложения и базы это почти замена compose файлу из двух строк.
Юниты Quadlet для автозапуска контейнеров в systemd
Флаг restart в Podman держит контейнер на плаву, пока жива сессия или процесс, но перезагрузку сервера он не переживёт: непривилегированному контейнеру нужен законный хозяин со стороны системы инициализации. Старый путь через podman generate systemd, генерировавший готовые юниты, командой помечен устаревшим, и на смену пришёл Quadlet: описание контейнера прямо в systemd, файлом с расширением container:
# ~/.config/systemd/user/web.container
[Unit]
Description=Статический сайт
[Container]
Image=docker.io/library/nginx:stable
PublishPort=8080:80
Volume=webdata:/usr/share/nginx/html:ro
[Service]
Restart=always
[Install]
WantedBy=default.target
Файл называется web.container, а юнит после перечитывания конфигурации превращается в web.service:
systemctl --user daemon-reload
systemctl --user enable --now web.service
systemctl --user status web.service
loginctl enable-linger $USER
Последняя команда обязательна: без неё юниты пользователя стартуют только после входа хозяина в систему, а сервер загружается головой без людей. Со включённым linger контейнеры поднимаются при загрузке сами. Для привилегированных контейнеров файлы кладут в /etc/containers/systemd, синтаксис тот же. Журналы при этом читаются двумя способами: podman logs web для быстрых строк и journalctl --user -u web.service, когда нужен контекст юнита с моментами старта и перезапусков.
У Quadlet есть и продолжение темы обновлений. Строка AutoUpdate=registry в секции Container разрешает следить за свежими версиями образа в реестре, а связка из команды podman auto-update и её таймера подтягивает обновления и перезапускает только те юниты, для которых это разрешено:
podman auto-update
systemctl --user list-timers
Таймер включается той же systemctl, и обновление образов превращается из ритуала в фоновую рутину. Для боевых сервисов обновление по расписанию выбирают осторожно, но для витрины или внутреннего сервиса с откатом через реестр это честная экономия вечера.
Перенос образов и типичные грабли переезда
Хранилища Docker и Podman не совместимы напрямую: у первого образы лежат в /var/lib/docker, у второго в пользовательских каталогах, и перекочевать они сами не смогут. Надёжный путь перекачать всё заново из реестра командой podman pull: слои, которых не хватает локально, доедут быстро, а кэш сэкономит трафик. Для локальных образов, которых нет в реестре, работает архив:
podman save --format docker-archive -o app.tar app:1.0
podman load -i app.tar
Архив формата docker archive понимает и docker load, так что обмен работает в обе стороны. Внутренние реестры компании подключаются конфигурацией registries.conf, где прописываются зеркала и порядок обращения, поэтому путь к образу в команде запуска менять не придётся. Отдельной строкой стоит знать про podman system migrate: после обновления пакетов команда мягко подтягивает работающие контейнеры к новой версии и перезапускает их юниты. Проверки здоровья из compose тоже поддерживаются, и флаг healthcheck-command задаёт их прямо в команде запуска.
Из граблей чаще всего стреляют три. Панели, жёстко привязанные к сокету Docker, на Podman не переедут: их место остаётся на старой системе. Изоляция по пользователям режет глаза админам, привыкшим видеть все контейнеры сразу: здесь у каждого своё хозяйство. И restart без systemd перестаёт означать автозапуск: привычка описывать контейнер через Quadlet должна стать рефлексом. После переезда прогоняют четыре проверки:
- podman ps и podman logs находят перенесённые контейнеры и их журналы;
- curl на опубликованный порт отвечает снаружи сервера, а не только с локалхоста;
- systemctl --user status подтверждает подъём юнитов Quadlet после перезагрузки;
- podman system df показывает новое хранилище без старых гигабайтов Docker.
Если все четыре зелёные, переезд закрыт. По сути, Podman забирает у Docker лучшее из повседневного, команду за командой и файл за файлом, и отдаёт взамен то, чего у старой системы не было, скромность в правах. Там, где нужна экосистема плагинов и панелей, Docker остаётся разумным выбором, а там, где важнее гигиена привилегий и предсказуемый systemd, выбор очевиден.