Capsule
Честно говоря, Capsule трудно описать в двух словах: прямого аналога у него нет, а область применения поначалу кажется размытой. Ближе всего будет Conty, но он заперт в своей игровой нише и работает только на базе arch. Когда не за что зацепиться, мне проще объяснять через знакомое. Поэтому возьмём трёх "соседей" - Flatpak, AppImage, Distrobox - и посмотрим, что у Capsule общего с каждым из них, а чем он отличается в лучшую или худшую сторону.
О проекте в сравнении
Flatpak использует bwrap-песочницу для изоляции. Capsule тоже. Под капотом тот же bubblewrap, те же пользовательские namespace'ы, та же логика «дать приложению ровно столько, сколько ему нужно для работы». Но без демона, без системного репозитория рантаймов и без отдельной установки.
Тут стоит честно сказать и об обратной стороне медали. По всему остальному Flatpak далеко впереди: у него огромная экосистема готовых приложений (Flathub), зрелая система разрешений на основе порталов - с тонким контролем доступа к файлам, камере, устройствам - и статус фактического стандарта в индустрии.
AppImage - это приложение, упакованное в один файл. Capsule тоже - но не всегда только одно приложение. AppImage кладёт в файл одну программу с её библиотеками, Capsule - целый контейнерный rootfs и сколько угодно программ поверх, а ярлыки умеет выставлять в меню хоста и убирать обратно одной командой.
Из минусов - капсулы заметно тяжелее: AppImage несёт только приложение с библиотеками, а Capsule - целый rootfs. Зато взамен он даёт то, чего у AppImage нет вовсе - настоящую изоляцию в bwrap-песочнице, как у Flatpak.
Distrobox работает с контейнерами поверх podman/docker. Capsule использует OCI-образ только как источник rootfs на этапе сборки. Distrobox держит живой контейнер в рантайме и без podman/docker не работает. Capsule же после сборки ни от какого движка не зависит: контейнер для него - строительные леса, а не способ доставки. Результат - самодостаточный ELF, который при запуске разворачивает песочницу и исчезает после закрытия приложений.
И ещё одно отличие, которое легко упустить. Другие форматы диктуют приложению форму - но это не каприз, а плата за их устройство. AppImage лежит поверх системы хоста, без своего rootfs, поэтому ему нужен AppRun как переходник к чужим библиотекам и .desktop в строго определённом месте. Flatpak делит мир на приложение и общий рантайм, а его песочница и порталы завязаны на app-id - без манифеста и идентификатора эта модель не работает. Capsule же несёт rootfs целиком, и договариваться с хостом ему не о чем: смонтировать корень и запустить исполняемый файл по пути - вот и весь контракт. Поэтому структура и не нужна: внутри обычная файловая система дистрибутива, и в капсулу можно завернуть что угодно - от одного исполняемого файла до целого окружения. Цена этой свободы - размер, а также несколько больше ограничений на управление приложениями внутри.
Если уложить в одну фразу: Capsule берёт OCI-образ и превращает его в портативный исполняемый файл, который запускается без root, без контейнерного движка и без зависимостей.
Мотивация
Для меня важно дать ALT Atomic ещё одно средство доставки, запуска и изоляции приложений без модификации системы. Но есть и второй мотив - воплотить свою детскую фантазию.
Когда-то в детстве, сидя за компьютером, я удалял ярлыки с рабочего стола и был уверен, что программы при этом по-настоящему исчезают из машины: пропала иконка - значит, пропало и всё остальное. Наивно, конечно - на диске оставались и сам исполняемый файл, и связанные с ним файлы, и записи в системе. Но в той детской картине мира было что-то верное: приложение виделось цельной вещью, которую можно взять и убрать одним движением, не оставив следов.
Повзрослев, я увидел, насколько реальность от этого далека. Программа размазана по десяткам каталогов, тянет за собой зависимости, прописывается в систему и оставляет хвосты даже после удаления. Capsule - отчасти попытка вернуть ту наивную простоту: пусть приложение действительно станет одной вещью, одним файлом, который появляется и исчезает целиком.
Контейнерный образ - это уже почти портативное приложение. В нём лежит полноценное окружение: библиотеки нужных версий, конфиги, исполняемые файлы. Не хватает ему лишь одного - самостоятельности. Образ не умеет запускать себя сам: он инертен, пока рядом нет реестра, откуда его тянуть, и движка, который развернёт его в работающий процесс.
Capsule даёт образу эти недостающие возможности - нести и запускать себя самому. Работу, которую прежде делали снаружи реестр и контейнерный движок, теперь берёт на себя небольшой рантайм, вшитый в сам файл: он находит внутри образ, монтирует его и поднимает песочницу. Поэтому подключать podman или docker, давать повышенные права и что-то устанавливать не требуется - приложение и правда становится той самой «одной вещью», одним файлом.
Детали реализации
Внутри Capsule - две роли, два разных исполняемых файла.
Первый - сборщик (capsule build). Он тянет OCI-образ через библиотеку buildah, монтирует его файловую систему, прогоняет по ней install-скрипты из YAML-манифеста (поставить пакеты, положить конфиги, почистить кэши), а затем упаковывает получившийся rootfs в сжатый образ SquashFS и собирает финальный файл.
Второй - рантайм (capsule-runtime). Это статически слинкованный исполняемый файл на Go, который во время сборки встраивается в каждую произведённую капсулу. Именно он отвечает за всё, что происходит при запуске.
Сам итоговый файл устроен, как слоёный пирог:
- Go-рантайм - исполняемая «голова» файла. То, что реально запускается, когда вы кликаете на капсулу.
- utils.tar.gz - вшитый набор инструментов:
bwrap,squashfuse,unionfs,mksquashfsи собственныйld-linuxс библиотеками. Благодаря этому капсуле не нужны системныеbubblewrapиsquashfuse- она носит их с собой. - binconfig - компактный JSON с манифестом: что запускать, какие ярлыки экспортировать, какой режим песочницы, переменные окружения.
- SquashFS - сжатый образ всего пользовательского окружения: библиотеки, исполняемые файлы, шрифты, приложение.
- Footer - 32 байта с магией
CAPSULE\0и смещениями, по которым рантайм находит остальные слои.
Что происходит при запуске, по шагам:
- Самочтение. Рантайм открывает
/proc/self/exe, читает 32-байтовый footer и узнаёт, где внутри него самого лежат binconfig и SquashFS. - Распаковка инструментов.
utils.tar.gzизвлекается во временный workspace - так появляютсяbwrapиsquashfuse, независимо от каких-либо системных компонентов. - Монтирование.
squashfuseмонтирует SquashFS прямо из тела исполняемого файла по смещению через FUSE - в пространстве пользователя, без root. - Слой записи. Поверх образа (он read-only) кладётся записываемый
unionfs-overlay. Всё, что приложение создаёт и меняет, оседает в этом слое и сохраняется между запусками. - Песочница.
bubblewrapчерез user namespaces строит изолированное окружение: корнем становится смонтированный rootfs, внутрь пробрасываются$HOME,/dev,/tmp(вместе с X11-сокетами),/run(вместе с Wayland- и пользовательскими сокетами), а поверх -/etc/resolv.conf,/etc/hosts,/etc/localtimeс хоста, чтобы работали сеть, имена и часовой пояс. - Мост наружу. Если в манифесте включён
host_exec(по умолчанию включен), рантайм поднимает abstract-UNIX-сокет и пробрасывает внутрь клиентcapsule-host-exec(а заодно подменяетxdg-openиgio), чтобы ссылка из капсульного браузера открывалась в системном, а не пыталась открыться внутри.
Степень изоляции выбирается одним параметром - sandbox:
По умолчанию - shared: максимальная совместимость, приложение почти не замечает песочницу. isolated нужен демонам (тому же nginx), которым требуется свой writable /run. strict вдобавок отрезает сеть - для совсем недоверенного софта.
Отдельно стоит упомянуть две вещи, делающие капсулы более самостоятельными:
commit- после того как вы вошли внутрь, доставили пакеты и настроили всё под себя, эта команда запекает накопленный overlay обратно в SquashFS прямо внутри файла. Капсула буквально переписывает саму себя.update- прогоняет заданный в манифесте скрипт обновления и коммитит результат, с возможностью автоматического отката, если что-то пошло не так.
Примеры использования
Установим в операционную систему Альт:
run0 apt-get install capsule
Удобнее всего смотреть на капсулу через две её части - они работают в разное время и выполняют разные задачи:
- Сборщик (
capsule build) - работает один раз на этапе сборки. Берёт OCI-образ, прогоняет по нему ваши install-шаги и запекает результат в готовый файл. - Рантайм (
capsule-runtime) - вшит в этот файл и включается потом, при каждом запуске: монтирует образ, поднимает песочницу, запускает приложение.
Манифест - это вход для сборщика. Вот рабочий пример: Firefox поверх capsule-base - базового образа, в котором уже лежат общие GUI-зависимости (mesa, wayland, шрифты, звук) и механизм авто-экспорта. Базовый образ может быть любым, в examples проекта есть разные примеры.
image: altlinux.space/dmitry/capsule-base/sisyphus:latest
output: ~/.local/bin/capsule_firefox
compression: zstd
host_exec: true
install:
- name: app
run: |
apt-get update -y
apt-get install -y firefox
- name: discover exports
run: |
/utils/generate-overrides
- name: cleanup
run: |
/utils/cleanup«Ручной» работы тут почти нет, и это заслуга базового образа. Раз GUI-зависимости уже в capsule-base, в манифесте остаётся поставить само приложение. А два инструмента из /utils (их кладёт туда capsule-base, не сам capsule) снимают остальную рутину:
/utils/generate-overridesсравнивает текущий список пакетов со слепком, который capsule-base снял при своей сборке (/utils/base-packages.txt). Разница — это то, что вы установили (в примере —firefoxи его зависимости). Для этих пакетов инструмент находит.desktop-файлы и бинарники и записывает их в/.capsule.overrides.yml;/utils/cleanupчистит кэши APT, логи и удаляет/utils, чтобы в финальный образ не попало лишнее.
Когда шаги отработали, сборщик подхватывает /.capsule.overrides.yml из корня rootfs, вливает его в конфиг капсулы (поля export и прочие) и удаляет файл, чтобы тот не остался в готовом образе. Дальше rootfs пакуется в SquashFS, в него встраивается рантайм — и капсула готова.
Если делать то же самое без capsule-base, пришлось бы вручную устанавливать все GUI-зависимости, перечислять экспорты и удалять ненужное. Манифест разросся бы примерно так:
image: registry.altlinux.org/sisyphus/base:latest
output: ~/.local/bin/capsule_firefox
compression: zstd
host_exec: true
export:
apps:
- desktop: /usr/share/applications/firefox.desktop
name_suffix: " (Capsule)"
binaries:
- /usr/bin/firefox
install:
- name: gui deps
run: |
echo 'RPM::Options:: "--ignoresize";' >> /etc/apt/apt.conf
apt-get update -y
apt-get install -y \
libEGL libGL libGLES libGLX-mesa libEGL-mesa mesa-dri-drivers \
xorg-dri-intel xorg-dri-radeon xorg-dri-nouveau xorg-dri-swrast \
libgbm libdrm \
libwayland-client libwayland-cursor libwayland-egl \
libxkbcommon libxkbcommon-x11 xorg-xwayland \
libnss libnspr libsecret libnotify \
fontconfig fonts-ttf-dejavu xkeyboard-config \
libalsa libpulseaudio pipewire-libs \
libva libva-driver-intel libva-intel-media-driver \
ca-certificates
- name: app
run: |
apt-get install -y firefox
- name: cleanup
run: |
apt-get clean
rm -rf /var/cache/apt/archives/*.rpm /var/cache/apt/*.bin /var/lib/apt/lists/*capsule build ./firefox.yaml -v
С готовой капсулой работает уже рантайм — всеми командами заведует он:
> capsule_firefox -h NAME: capsule - Рантайм портативного Linux-контейнера USAGE: capsule [global options] [command [command options]] VERSION: 0.3.6 COMMANDS: shell, s Запустить интерактивный shell внутри капсулы mount-only Смонтировать squashfs и вывести точку монтирования export Экспортировать приложения и исполняемые файлы в хост-систему (all|apps|binaries) unexport Удалить экспортированные приложения и исполняемые файлы (all|apps|binaries) commit Зафиксировать изменения overlay в squashfs-образе update Выполнить заданный update-скрипт и зафиксировать результат clean Удалить данные overlay (сбросить капсулу к исходному состоянию) GLOBAL OPTIONS: --bind SRC[:DST], -b SRC[:DST] [ --bind SRC[:DST], -b SRC[:DST] ] Смонтировать путь с хоста внутрь капсулы (SRC[:DST], можно повторять) [$CAPSULE_BIND] --env KEY=VAL, -e KEY=VAL [ --env KEY=VAL, -e KEY=VAL ] Задать переменную окружения внутри капсулы (KEY=VAL, можно повторять, перекрывает конфиг) [$CAPSULE_ENV] --unsetenv KEY, -u KEY [ --unsetenv KEY, -u KEY ] Удалить переменную окружения внутри капсулы (KEY, можно повторять) [$CAPSULE_UNSETENV] --home PATH Переопределить домашний каталог капсулы (PATH) [$CAPSULE_HOME] --verbose, -v Включить отладочные логи [$CAPSULE_DEBUG] --no-overlay Отключить unionfs-overlay (rootfs только для чтения) [$CAPSULE_NO_OVERLAY] --no-nvidia Пропустить проброс драйверов NVIDIA [$CAPSULE_NO_NVIDIA] --squashfuse auto|3|ll FUSE-бэкенд для squashfs: auto|3|ll (3 экономнее, ll быстрее) [$CAPSULE_SQUASHFUSE] --sandbox shared|isolated|strict Уровень изоляции: shared|isolated|strict (переопределяет конфиг) [$CAPSULE_SANDBOX] --help, -h Показать помощь --version Показать версию
Сборщик капсул содержит следующие команды:
> capsule -h NAME: capsule - Создание портативных Linux-контейнеров из OCI-образов USAGE: capsule [global options] [command [command options]] VERSION: 0.3.6 DESCRIPTION: capsule — инструмент для создания портативных Linux-контейнеров в виде единого ELF-файла. Читает YAML-конфиг с описанием образа и команд, на выходе — самодостаточный исполняемый файл. COMMANDS: build Собрать портативный контейнер из OCI-образа list Список установленных капсул update Пересобрать установленные капсулы из их исходного YAML GLOBAL OPTIONS: --help, -h Показать помощь --version Показать версию
Витрина капсул: capsulepack.dev
Писать манифест руками нужно не всегда. Для тех, кто просто хочет приложение, есть capsulepack.dev — веб-витрина и конфигуратор капсул. Это каталог на несколько тысяч приложений (браузеры, среды разработки, игры, утилиты) на базе Sisyphus от ALT Linux Team, а также некоторые приложения из репозитория Aides.
Сам сайт ничего не собирает у себя. Он генерирует и хостит YAML-манифест, а сборка идёт локально на вашей машине — тем же capsule build, что описан выше.Дальше всё как обычно: capsule_generate export, capsule_generate shell и так далее. На странице капсулы видно, что войдёт внутрь и сколько это весит (приблизительно), а сам манифест можно скачать и поправить под себя. По сути это удобная обёртка для того, что было показано выше.
Немного сравнений
Возьмём один и тот же Firefox в трёх форматах.
Цифры честно показывают, где чья ниша. Самое наглядное - размер:
- AppImage самый лёгкий: он несёт только саму программу и её библиотеки, опираясь на системные компоненты хоста.
Цена лёгкости — меньшая герметичность и одна программа на файл. - Flatpak выглядит тяжелее всех, но это обманчиво: его 1,33 ГБ - это в основном общий рантайм (
Platform,GL.default), который скачивается один раз и переиспользуется всеми Flatpak-приложениями. Второе и третье приложение «весят» уже немного.
Цена — нужен установленный Flatpak, его демон и репозитории рантаймов. - Capsule находится посередине: он крупнее AppImage, потому что несёт полноценное окружение целого дистрибутива, а не только библиотеки. Но за счёт этого достигается самодостаточность: никакого общего рантайма, никакого движка, никакой установки. 391 МБ — это весь Firefox со своим окружением, который можно положить на флешку и запустить на любой Linux-машине без зависимостей. А если положить в ту же капсулу второе приложение, они разделят уже загруженное окружение.
За самодостаточность Capsule приходится платить. Во-первых, весом: один файл несёт целое окружение дистрибутива, поэтому он заметно тяжелее AppImage (в нашем примере 391 МБ против 137 МБ). Во-вторых, стартом: при каждом запуске рантайм распаковывает встроенные утилиты во временный каталог, монтирует SquashFS через FUSE и поднимает песочницу bwrap — это милисекунды, зависимые от типа компрессии (zstd, lz4), аргументов запуска (--no-overlay) и т.д. Незаметные для долгоживущего приложения, но это накладные расходы, которых у нативного исполняемого файла нет. В данном случае мы осознанно меняем лёгкость на герметичность и портативность.
Поэтому «лучшего» формата здесь нет — есть подходящий под задачу:
- Нужен минимальный вес и одна программа — AppImage вне конкуренции;
- Нужно много приложений с общим рантаймом в управляемой экосистеме - это Flatpak;
- Нужна интерактивная dev-среда поверх живого контейнера - Distrobox.
А когда нужен самодостаточный, изолированный набор приложений из OCI-образа, который можно запустить где угодно без установки и без повышенных привилегий — Capsule оказывается на своём месте.