pullrun
- title
- pullrun
- type
- toolbox
- summary
- Запускает OCI-образ как runc-контейнер, microVM Firecracker или VM Apple Silicon из контентно-адресуемого DAG-хранилища
- tags
- rust, golang, containers, microvm, oci, p2p, sandboxing, watchlist
- language
- Rust (runtime), Golang (CLI, CRI shim, compose)
- license
- Apache-2.0
- created
- 2026-07-29
- updated
- 2026-07-29
- lang
- ru
- translation_of
- pullrun
- source_updated
- 2026-07-29
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Pullrun берёт обычный OCI-образ и запускает его тремя способами: как контейнер runc, как microVM Firecracker или как виртуальную машину Apple Virtualization на Apple Silicon. Один и тот же образ, одна и та же команда, одно локальное хранилище - меняется только флаг --backend. Отдельного этапа сборки VM-образа и специфичного для виртуальных машин формата нет, поскольку OCI-манифест напрямую используется как rootfs для VM.
pullrun run alpine:3.18 --cmd "echo" --cmd "hello" # container (Linux)
pullrun run alpine:3.18 --backend vm --cmd "echo" --cmd "hello" # Firecracker microVM
pullrun run alpine:3.18 --cmd "echo" --cmd "hello" --attach -t # Apple VM (macOS default)
В этом вся суть, и проект опирается на тот же аргумент, что и microvm-2026: контейнер - это механизм контроля ресурсов, а не граница безопасности, поэтому выбор между изоляцией на общем ядре и отдельным ядром на VM должен быть флагом времени выполнения, а не отдельным набором инструментов. Если matryoshka-isolation описывает бутерброд "контейнеры внутри VM", на котором останавливается большинство платформ, то pullrun делает их равноправными соседями на базе одного хранилища.
DAG-хранилище
Архитектура хранилища - то, ради чего стоит читать этот проект. Вместо распаковки слоёв через overlayfs pullrun хранит каждый OCI-слой как есть в контентно-адресуемом направленном ациклическом графе с ключом по дайджесту sha256: и делает mmap() блобов при чтении. Реализация на Rust использует rkyv для десериализации без копирования, memmap2 для отображения в память и кэш DashMap<Digest, Arc<Mmap>>, благодаря чему первый читатель платит за mmap() и page fault'ы, а все последующие обходятся одной атомарной операцией загрузки.
Из контентной адресации следуют две вещи. Каждый узел, скачавший alpine:3.18, хранит побайтово идентичные файлы, именно это делает уровень peer-to-peer синхронизации дешёвым - блоки проверяются по хешу при получении, поэтому пиру не обязательно доверять, достаточно иметь к нему доступ. А удаление превращается в задачу на графах, а не в файловой системе: pullrun rmi каскадно удаляет недостижимое поддерево, отдельные sidecar-файлы со счётчиками ссылок на узле сохраняют слои, разделяемые с другим образом, пока не исчезнет последняя ссылка, а проход recompute_all_refcounts при запуске демона пересчитывает счётчики после сбоя. pullrun gc вычищает всё, что больше недостижимо из тегированного образа или работающей нагрузки, по умолчанию в режиме dry-run с 90%-м защитным порогом, который нужно принудительно обходить через --force.
Отказ от overlayfs также преподносится как аргумент в пользу безопасности: в README упоминаются CVE-2023-0386 и CVE-2023-32629 как побеги из контейнера через overlayfs, с которыми хранилище, не создающее overlay, просто не может столкнуться. Далее приводится баг в page cache ядра, CVE-2026-31431 ("Copy Fail"), как пример проблемы общего ядра, которую не может решить ни один драйвер хранилища, но решают изолированные ядра виртуальных машин, - что и служит обоснованием самого существования бэкендов на базе VM. Оба утверждения взяты из собственного README проекта; для второй CVE никаких других источников не приведено.
Бэкенды и цифры
| Backend | Isolation | Platform | Claimed boot |
|---|---|---|---|
| runc | namespaces | Linux, macOS, Windows (WSL2) | ~400 ms |
| Firecracker | отдельное ядро на VM (KVM) | Linux x86_64, WSL2 x86_64 | ~500 ms холодный старт, ~200 ms пул разогретых |
| Apple Virtualization | отдельное ядро на VM (Hypervisor.framework) | macOS Apple Silicon | ~160 ms |
Наряду с этим README заявляет 968 мс на первый pull alpine:3.18 против ~2 с у Docker, 24.6 MiB RSS у простаивающего демона против ~90 MiB и ~20 MB стрипнутых бинарников против ~75 MB. Это собственные бенчмарки проекта, прогнанные через hyperfine на M3 против Docker Desktop 4.27 со скриптом, опубликованным в hack/bench.sh, - воспроизводимые в теории, но никем другим пока не проверенные. Цифра для тёплого пула наиболее интересна, так как здесь используется тот же трюк со snapshot-restore в Firecracker, позволяющий обойти нижний порог времени холодной загрузки, описанный в microvm.
Поведение после stop различается в зависимости от бэкенда сильнее, чем время загрузки: VM в Apple Virt сохраняет rootfs через VirtioFS, а VM в Firecracker сохраняет свой ext4-образ, поэтому обе перезапускаются как машины после гибернации, тогда как rootfs нагрузки в runc эфемерна и сохраняются только монтирования --volume.
Всё остальное в бинарнике
Функциональная поверхность для столь молодого проекта весьма широка. Здесь есть встроенный сборщик Dockerfile, который вызывает runc для выполнения инструкций RUN и кэширует слои по хешу инструкций, так что сборкам не нужен демон Docker. Отдельный бинарник pullrun-compose парсит стандартный docker-compose.yml через compose-spec/compose-go и умеет запускать каждый сервис из файла как microVM с помощью --backend vm. Сами ядра тоже представляют собой OCI-артефакты: pullrun kernel install скачивает vmlinux из реестра в DAG-хранилище, а --kernel-image указывает нагрузке на нужное ядро по ссылке.
Движок политик проверяет нагрузки перед запуском: верификация подписей Cosign, оценка SBOM с порогами CVSS и чёрными списками лицензий, профили seccomp, rootfs только для чтения, no_new_privileges - всё это декларируется как required_signature: true, max_cvss_score: 7.0, deny_licenses: ["GPL-3.0"]. Уровень P2P обнаруживает пиры через mDNS, распространяет состояние через gossip-протокол и использует фильтры Блума, чтобы не пересылать блоки, которые у пира уже есть, благодаря чему кластер скачивает данные из реестра один раз, а остальное дельта-синхронизирует на скорости локальной сети. Есть CRI-shim для Kubernetes (бета), предоставляющий RuntimeClasses pullrun-container и pullrun-vm, а также MCP-сервер с 15 операциями среды выполнения и четырьмя типами ресурсов, что ставит его на ту же позицию инструмента для агентов, что и Sandbox Agent SDK от Rivet, только API у pullrun документирован, а не отреверсен.
На что обратить внимание
Репозиторий был создан 2026-06-11 и к концу июля 2026 года набрал 114 звёзд, один форк и ноль открытых issue. Каждая цифра, ссылка на CVE и утверждение в духе "единственный runtime, умеющий X" на этой странице взяты из README, написанного самими авторами проекта; независимых подтверждений здесь нет. Внесение правок требует подписания CLA, что намекает на стремление контролировать лицензирование в проекте под Apache-2.0. Control plane явно находится в разработке (etcd, DNS и admission control помечены как "planned"), CRI-shim обозначен как бета, а разделение между Rust и Golang на одиннадцать crate'ов и четыре компонента на Golang - это слишком большая поверхность для, судя по всему, очень маленькой команды.
По этой причине странице присвоен тег watchlist - критерии повторной проверки описаны в watchlist. Идея с хранилищем достаточно хороша, чтобы её позаимствовать, независимо от того, выживет ли эта реализация.
Apache-2.0, ~114★ на 2026-07-29. Технический отчёт заархивирован на doi.org/10.5281/zenodo.20679669.