Quadlet — это генератор systemd, который входит в состав Podman и превращает декларативные файлы в обычные службы. В 2026 году это основной способ автозапуска контейнеров: старый podman generate systemd объявлен устаревшим, а Quadlet не требует отдельных пакетов и работает как для root, так и для обычного пользователя. Ниже — практическая схема от первого файла до диагностики, обновления образов и ограничения прав.
Как Quadlet превращает файл в сервис
Генератор читает файлы с расширениями .container, .volume, .network, .pod, .build, .image и .kube при загрузке системы и при каждом systemctl daemon-reload. Из файла web.container получается служба web.service. Для rootful-режима файлы лежат в /etc/containers/systemd/, для rootless — в ~/.config/containers/systemd/. Временные файлы можно класть в /run/containers/systemd/, но после перезагрузки они исчезнут.
Quadlet требует cgroup v2. Проверьте это до начала работы, иначе контейнер может запуститься, но управление ресурсами и часть настроек systemd будут вести себя непредсказуемо.
podman info --format '{{.Host.CgroupsVersion}}'
mkdir -p ~/.config/containers/systemd
systemctl --user daemon-reload
systemctl --user is-system-running
Минимальный файл .container
Обязателен только Image, но для рабочего сервиса сразу задайте имя, порт и том. Директивы [Container] почти один в один соответствуют ключам podman run, а секции [Unit], [Service] и [Install] достаются systemd без изменений.
[Unit]
Description=Web frontend
[Container]
Image=docker.io/library/nginx:1.27-alpine
ContainerName=web
PublishPort=127.0.0.1:8080:80
Volume=web-data.volume:/usr/share/nginx/html:Z
[Service]
Restart=always
[Install]
WantedBy=default.target
После сохранения выполните daemon-reload и запустите службу. Важная особенность: включить сгенерированный юнит через systemctl enable нельзя — он создаётся генератором на лету и не лежит постоянным файлом в /etc/systemd/system. Вместо этого генератор сам применяет секцию [Install] и создаёт зависимости при загрузке, поэтому строки WantedBy=default.target достаточно.
systemctl --user daemon-reload
systemctl --user start web.service
systemctl --user status web.service --no-pager
journalctl --user -u web.service -n 50 --no-pager
Если система пишет Unit web.service not found, значит генератор не смог разобрать файл. Типичные причины — опечатка в имени директивы, ключ из более новой версии Podman или отсутствующий образ.
Тома, сети и pod
Именованный том удобнее описывать отдельным файлом .volume: тогда Podman создаст его до запуска контейнера и свяжет службы зависимостью. В ссылке указывается имя файла с расширением, а не имя тома. Сеть описывается так же — отдельным .network.
[Volume]
VolumeName=web-data
[Network]
NetworkName=appnet
Subnet=10.89.10.0/24
В контейнере используйте Volume=web-data.volume:/data:Z и Network=appnet.network. Суффикс :Z включает приватную SELinux-метку; на системах без SELinux он игнорируется. Для пода создайте app.pod и подключите контейнеры через Pod=app.pod — так они разделят сетевой namespace и порты.
[Pod]
PodName=app
PublishPort=127.0.0.1:8080:80
[Container]
Image=docker.io/library/nginx:1.27-alpine
Pod=app.pod
Не публикуйте порты на 0.0.0.0 без необходимости. Привязка 127.0.0.1:8080:80 оставляет сервис доступным только локально, а наружу его лучше отдавать через reverse proxy.
Автозапуск без входа в систему
Rootless-службы живут в пользовательском менеджере systemd и останавливаются, когда пользователь выходит из системы. Чтобы контейнер пережил logout и запускался после перезагрузки, включите linger.
loginctl enable-linger "$USER"
loginctl show-user "$USER" -p Linger
systemctl --user daemon-reload
systemctl --user is-system-running
Для rootful-режима файлы кладут в /etc/containers/systemd/, а в [Install] указывают WantedBy=multi-user.target. Права на файлы должны быть 0644, владелец root:root. Не держите в юните пароли и токены: используйте EnvironmentFile с правами 0600 или Podman secrets.
Healthcheck и обновление образов
AutoUpdate=registry заставляет podman auto-update проверять реестр. Это удобно для домашних сервисов, но для критичных систем лучше фиксировать тег или digest и обновляться вручную после чтения changelog. Healthcheck позволяет systemd перезапускать контейнер, который жив, но не отвечает.
[Container]
Image=docker.io/library/nginx:1.27-alpine
AutoUpdate=registry
HealthCmd=wget -q -O /dev/null http://localhost/ || exit 1
HealthInterval=30s
HealthRetries=3
HealthOnFailure=kill
Проверьте таймер автообновления и заранее посмотрите, что будет обновлено. Команда podman auto-update --dry-run ничего не меняет и показывает план.
podman auto-update --dry-run
systemctl --user list-timers podman-auto-update.timer
podman healthcheck run web
podman inspect web --format '{{.State.Health.Status}}'
Диагностика
Самый полезный инструмент — dryrun генератора: он печатает, какие юниты будут созданы, и показывает ошибки разбора. Путь к генератору зависит от дистрибутива. systemd-analyze с флагом --generators=true дополнительно проверяет сгенерированный юнит целиком. Дальше смотрите статус службы и логи Podman.
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun
systemd-analyze --user --generators=true verify web.service
podman ps --format '{{.Names}} {{.Status}}'
podman logs --tail 50 web
Если контейнер стартует, но приложение недоступно, проверьте, слушает ли оно нужный адрес внутри образа, и совпадает ли опубликованный порт. podman inspect показывает фактические переменные окружения и монтирования, а не только то, что написано в файле.
Безопасность и типичные ошибки
Главное преимущество Quadlet — возможность запускать контейнеры без root. Дополнительно закройте лишние возможности: ReadOnly=true, DropCapability=ALL, NoNewPrivileges=true, Tmpfs=/tmp. Не используйте --privileged и монтирование docker.sock: это фактически отдаёт контейнеру управление хостом.
Отдельно задайте лимиты: Memory=, PidsLimit= и TimeoutStartSec. Без них контейнер с утечкой памяти выест хост, а первый запуск с загрузкой большого образа упрётся в стандартные 90 секунд и завершится ошибкой. TimeoutStartSec=900 в секции [Service] даёт запас на pull. Для наблюдения используйте systemd-cgtop и podman stats: они показывают, какой юнит действительно расходует ресурсы, и помогают отличить проблему приложения от нехватки прав.
- Ключ
User=в секции[Service]не понижает права контейнера: rootless-режим определяется тем, от какого пользователя запущен генератор. Внутри образа используйтеUser=иGroup=из[Container]. - Для томов на SELinux ставьте
:Z, иначе приложение получитpermission denied. - Порты ниже 1024 в rootless-режиме требуют настройки
net.ipv4.ip_unprivileged_port_start; проще слушать 8080 и проксировать. - После правки файла всегда выполняйте
daemon-reload, иначе изменится только исходник, а не служба.
Итог
Quadlet даёт предсказуемый автозапуск без shell-обёрток и cron. Начните с rootless-файла, проверьте dryrun, включите linger и только потом добавляйте healthcheck и автообновление. Такой порядок экономит время при отладке и не оставляет контейнеры, которые «вроде запущены», но не переживают перезагрузку.