В одной из прошлых статей мы рассказывали об альтернативе Docker – Podman. Одна из ключевых особенностей Podman – возможность запуска контейнеров, не требующих прав суперпользователя для своей работы, так называемых rootless-контейнеров. В этой статье мы рассмотрим, как запускать контейнеры и поды от имени пользователя, не имеющего root-права. В качестве хоста будет использоваться виртуальный сервер с операционной системой Ubuntu 24.04.
Когда стоит использовать rootless-контейнеры
Основная причина использовать контейнеры, не имеющие прав суперпользователя (rootless containers), – повышение уровня безопасности системы. Как и обычные пользователи, rootless-контейнеры не могут получить доступ к ресурсам, требующим root-права – если приложение в контейнере будет взломано, вредоносный процесс не получит привилегий в системе.
Запуск данных контейнера (podman container) будет невозможен в следующих ситуациях:
- если контейнеру требуется записывать данные в файловую систему хоста (контейнер может записывать данные только туда, куда у него есть права как у обычного пользователя);
- если контейнеру необходим для работы порт меньше, чем 1024.
Установка Podman
Инструкция по установке Podman на популярные дистрибутивы доступна в нашей статье.
Проверьте, что Podman установлен в системе:
podman -vТакже необходимо установить специальную утилиту, обеспечивающую сетевой доступ для rootless-контейнеров. На данный момент инструмент Podman поддерживает две такие утилиты: slirp4netns и pasta. В нашем примере будем использовать slirp4netns.
Для установки на Ubuntu используйте следующую команду:
sudo apt install slirp4netns
Также установим пакет systemd-container, содержащий утилиту machinectl. Мы будем использовать ее для подключения к пользователям без пароля.
sudo apt install systemd-containerТеперь мы можем перейти к созданию непривилегированного пользователя и развертыванию контейнера.
Запуск rootless-контейнера
Создание пользователя
Создадим нового пользователя для запуска контейнера с веб-сервером Nginx. Для максимальной безопасности мы рекомендуем создавать нового пользователя для каждого контейнера или пода.
useradd -m nginx
Параметр -m создает домашнюю директорию для пользователя – она необходима для корректной работы контейнера.
Далее необходимо разрешить запуск сервисов пользователям, не вошедшим в систему:
sudo loginctl enable-linger nginxТеперь подключитесь к пользователю:
sudo machinectl shell nginx@
Далее вы можете запустить контейнер при помощи podman run, однако мы рекомендуем запускать контейнер как сервис systemd.
Создание Quadlet
Чтобы запустить контейнер как systemd-сервис, мы будем использовать Quadlet. Данный механизм позволяет описывать контейнеры и поды в виде файлов .container и .pod, на основании которых генерируется systemd-юнит. Юниты systemd бывают различных типов, каждый из них выполняет определенную задачу, при этом имя каждого файла юнита включает соответствующее расширение (к примеру, nginx.service).
Такой подход позволяет разворачивать контейнеры без необходимости прописывать команду для запуска в директиву ExecStart.
Для rootless-контейнеров quadlet-файлы могут быть расположены в следующих директориях:
~/.config/containers/systemd//etc/containers/systemd/users/– путь для всех пользователей/etc/containers/systemd/users/$UID– путь для пользователя с соответствующим UID. Посмотреть UID можно следующей командой:echo $UID
В нашем примере мы будем размещать quadlet-файлы в поддиректориях .config домашней директории, которые сперва необходимо создать и затем перейти в них:
mkdir -p ~/.config/containers/systemd && cd ~/.config/containers/systemd/
Теперь, пользуясь любым текстовым редактором, создайте и откройте файл nginx.container.
Для написания quadlet удобно использовать таблицу из официальной документации, в которой опции файла .container сопоставлены с опциями команды podman run.
Вы также можете использовать утилиту podlet для генерации quadlet. Инструкции приведены в соответствующем разделе.
Quadlet для непривилегированного изображения nginx будет выглядеть следующим образом:
[Container]
ContainerName=nginx
Image=docker.io/nginxinc/nginx-unprivileged
PublishPort=8080:8080Далее настроим параметр, предписывающий перезагружать сервис в случае его завершения:
[Service]
Restart=always
TimeoutStartSec=90
Параметр TimeoutStartSec задает максимальное время, по истечении которого systemd считает старт неудачным.
Если параметр Restart имеет значение always, в случае неудачного запуска сервис будет перезагружен.
Чтобы quadlet запускался автоматически при старте системы, добавим дополнительный раздел:
[Install]
WantedBy=default.target
Если этот раздел добавлен, включать сервис командой systemctl enable не требуется. Готовый файл будет выглядеть следующим образом:
[Container]
ContainerName=nginx
Image=docker.io/nginxinc/nginx-unprivileged
PublishPort=8080:8080
[Service]
Restart=always
TimeoutStartSec=90
[Install]
WantedBy=default.target
Теперь обновите системные демоны:
systemctl --user daemon-reload
И выполните запуск сервиса:
systemctl --user start nginx
.service, указывать расширение файла при запуске контейнера Podman не нужно.Проверим, что сервис работает:
$ systemctl --user status nginx
● nginx.service
Loaded: loaded (/home/nginx/.config/containers/systemd/nginx.container; generated)
Active: active (running) 23min ago
Веб-сервер по порту 8080 также доступен:

Запуск rootless-пода
.pod, начиная с версии 5.0.0. Проверить версию podman вы можете командой podman -v.Попробуем запустить под путем создания юнита systemd на примере Jitsi Meet. Процесс аналогичен созданию quadlet для единичного контейнера, но появляется файл пода с расширением .pod.
Сперва сформируем quadlet для пода, указав в нем название пода, сетевые настройки для всех контейнеров (в самих контейнерах указывать порты для работы не потребуется).
jitsi.pod:
[Pod]
PodName=jitsi
Network=slirp4netns:port_handler=slirp4netns
PublishPort=5086:80
PublishPort=10000:10000/udp
PublishPort=5087:5280
PublishPort=50878:5280
AddHost=xmpp.meet.jitsi:127.0.0.1
AddHost=jvb.meet.jitsi:127.0.0.1
AddHost=meet.jitsi:127.0.0.1
Таблица директив для файла .pod также доступна в официальной документации.
Теперь создадим файл .container для каждого контейнера в поде.
jitsi-jicofo.container:
jitsi-jicofo.container:
[Container]
ContainerName=jitsi-jicofo
Pod=jitsi.pod
Image=docker.io/jitsi/jicofo:stable
AutoUpdate=registry
Volume=/home/jitsi/.config/containers/systemd/jicofo:/config:Z
EnvironmentFile=/home/jitsi/.config/containers/systemd/.env
[Service]
Restart=always
TimeoutStartSec=90
[Install]
WantedBy=default.target
jitsi-jvb.container:
[Container]
ContainerName=jitsi-jvb
Pod=jitsi.pod
Image=docker.io/jitsi/jvb:stable
AutoUpdate=registry
Volume=/home/jitsi/.config/containers/systemd/jvb:/config:Z
EnvironmentFile=/home/jitsi/.config/containers/systemd/.env
[Service]
Restart=always
TimeoutStartSec=90
[Install]
WantedBy=default.target
jitsi-prosody.container:
[Container]
ContainerName=jitsi-prosody
Pod=jitsi.pod
Image=docker.io/jitsi/prosody:stable
AutoUpdate=registry
Volume=/home/jitsi/.config/containers/systemd/config:/config:Z
Volume=/home/jitsi/.config/containers/systemd/prosody-plugins-custom:/prosody-plugins-custom:Z
EnvironmentFile=/home/jitsi/.config/containers/systemd/.env
[Service]
Restart=always
TimeoutStartSec=90
[Install]
WantedBy=default.target
jitsi-web.container:
[Container]
ContainerName=jitsi-web
Pod=jitsi.pod
Image=docker.io/jitsi/web:stable
AutoUpdate=registry
Volume=/home/jitsi/.config/containers/systemd/web:/config:Z
Volume=/home/jitsi/.config/containers/systemd/transcripts:/usr/share/jitsi-meet/transcripts:Z
EnvironmentFile=/home/jitsi/.config/containers/systemd/.env
[Service]
Restart=always
TimeoutStartSec=90
[Install]
WantedBy=default.target
Обновите системные демоны:
systemctl --user daemon-reload
Запустите под. Так как на основе quadlet-файла будет сформирован сервис с расширением .service, укажите имя пода без расширения:
systemctl --user start jitsi-pod
Проверим статус сервиса:
$ systemctl --user status jitsi-pod
● jitsi-pod.service
Loaded: loaded (/home/jitsi/.config/containers/systemd/jitsi.pod; generated)
Drop-In: /usr/lib/systemd/user/service.d
└─10-timeout-abort.conf
Active: active (running) since 2min 32s ago
Генерация Quadlet с помощью Podlet
Чтобы не переписывать compose в quadlet вручную, вы можете использовать утилиту podlet – она позволяет генерировать quadlet как из строки, так и из файлов compose. Рассмотрим варианты использования podlet на примере веб-сервера Caddy.
Установка podlet
Для установки podlet загрузите последний релиз для вашей платформы со страницы загрузок:
wget https://github.com/containers/podlet/releases/latest/download/podlet-x86_64-unknown-linux-gnu.tar.xz
Распакуйте загруженный архив и перейдите в директорию с файлами:
tar -xvf podlet-x86_64-unknown-linux-gnu.tar.xz && cd podlet-x86_64-unknown-linux-gnu
Дайте файлу права на исполнение и скопируйте в директорию /usr/bin:
sudo chmod +x podlet && sudo cp podlet /usr/bin/
Проверим работу утилиты:
$ podlet -V
podlet 0.3.0Теперь можно вернуться назад и удалить загруженный архив и распакованную директорию:
cd .. && rm -rf podlet-x86_64-unknown-linux-gnu*
Выполните podlet --help, чтобы посмотреть полный список команд.
Генерация quadlet из compose
Множество контейнерных приложений разворачиваются с использованием docker compose – как правило, пример такого файла указан в официальной документации приложения.
Для примера сгенерируем quadlet из compose.yaml файла Caddy, приведенного в официальной документации:
name: caddy
services:
caddy:
image: docker.io/library/caddy:latest
ports:
- 8000:80
- 8443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:Z
- caddy-data:/data
volumes:
caddy-data:
podlet compose ищет в текущей директории файлы со следующими названиями:
compose.yamlcompose.ymldocker-compose.yamldocker-compose.yml
В результате выполнения мы получим готовый quadlet:
$ podlet compose
# caddy.container
[Container]
Image=docker.io/library/caddy:latest
PublishPort=8000:80
PublishPort=8443:443
Volume=./Caddyfile:/etc/caddy/Caddyfile:Z
Volume=caddy-data:/data
Запишем его в файл caddy.container:
podlet compose > caddy.container
При добавлении параметра --pod будет сформирован pod-файл:
$ podlet compose --pod
# caddy-caddy.container
[Container]
Image=docker.io/library/caddy:latest
Pod=caddy.pod
Volume=./Caddyfile:/etc/caddy/Caddyfile:Z
Volume=caddy-data:/data
---
# caddy.pod
[Pod]
PublishPort=8000:80
PublishPort=8443:443
Вы также можете указать конкретный файл compose в команде:
$ podlet compose compose.yaml
Генерация quadlet через терминал
Для генерации quadlet из строки используйте следующую конструкцию:
$ podlet --file . --install --description Caddy \
podman run \
--restart always \
-p 8000:80 \
-p 8443:443 \
-v ./Caddyfile:/etc/caddy/Caddyfile:Z \
-v caddy_data:/data \
docker.io/library/caddy:latest
В результате в текущей директории будет сформирован файл caddy.container:
Wrote to file: ./caddy.container
Обновление образа контейнера
Автоматическое обновление
Для автоматического обновления запустите контейнер со следующей директивой:
AutoUpdate=registry
Затем активируйте сервис podman-auto-update для выбранного пользователя:
systemctl --user enable --now podman-auto-update.service
Сервис podman-auto-update.timer будет запускать этот сервис раз в сутки в полночь. Вы также можете запустить обновление вручную командой systemctl --user start podman-auto-update.service.
Ручное обновление
Перед началом обновления остановите контейнер:
systemctl --user stop nginx
Для просмотра используемого контейнерами образа можно применять следующую команду:
$ podman ps -a --format "{{.Image}}"
docker.io/nginxinc/nginx-unprivileged:latest
Выполните pull актуальной версии образа:
podman pull docker.io/nginxinc/nginx-unprivileged:latest
Чтобы выполнить pull образов для нескольких контейнеров, удобно использовать следующую команду:
podman ps -a --format "{{.Image}}" | sort -u | xargs -n1 podman pull
После загрузки образов перезапустите контейнер либо под и проверьте статус сервиса:
$ systemctl --user restart nginx
$ systemctl --user status nginx
Если возникнут вопросы, напишите нам, пожалуйста, тикет из панели управления аккаунта (раздел “Помощь и поддержка”), а если вы захотите обсудить эту статью, программирование или наши продукты с коллегами по цеху и сотрудниками облачной IT-платформы Beget – ждем вас в нашем сообществе в Telegram.