메뉴
HN
Hacker News 5일 전

단일 OCI 이미지로 컨테이너와 마이크로 VM을 모두 실행하는 'Pullrun'

IMP
8/10
핵심 요약

Pullrun은 동일한 OCI 이미지를 사용하여 일반 컨테이너, Firecracker 마이크로VM, Apple Silicon VM, 쿠버네티스 워크로드 등 다양한 환경에서 애플리케이션을 실행할 수 있게 해주는 오픈소스 런타임 및 CLI 도구입니다. 개발, 프로덕션, 보안 격리, AI 에이전트 환경마다 서로 다른 이미지 포맷과 실행 엔진을 유지해야 했던 기존 인프라의 복잡성을 25MB 미만의 단일 런타임으로 통합하여 혁신적으로 단순화했다는 점이 핵심입니다. 또한 P2P 이미지 분산과 콘텐츠 주소 지정(Content-addressed) 스토리지를 지원하여 클러스터 환경에서의 배포 속도와 효율성을 크게 높였습니다.

번역된 본문

Pullrun: 단 하나의 OCI 이미지. 모든 실행 환경 지원. 25MB 미만의 CLI 및 런타임 제공.

동일한 OCI 이미지를 컨테이너, Firecracker 마이크로VM, Apple Silicon VM, 쿠버네티스 워크로드 또는 AI 에이전트 태스크로 실행할 수 있습니다. 별도의 데몬이 필요 없으며, overlayfs를 사용하지 않고, 개별 VM 이미지를 따로 만들 필요도 없습니다.

Pullrun이란? Pullrun은 동일한 OCI 이미지를 컨테이너나 VM으로 실행합니다. 레이어를 콘텐츠 주소 지정 DAG에 저장(overlayfs 사용 안 함)하며, 블록을 P2P로 동기화하고, 약 14MB의 CLI와 6MB의 런타임 데몬으로 제공됩니다.

왜 이것이 중요한가? 현대의 인프라는 너무 많은 실행 엔진을 사용합니다. 개발용 Docker, 프로덕션용 containerd, 격리를 위한 Firecracker, 쿠버네티스를 위한 CRI, AI를 위한 MCP 에이전트 등이 있습니다. 이들은 모두 동일한 OCI 이미지를 실행함에도 불구하고, 각각 고유한 이미지 형식, 스토리지 및 운영 모델을 가지고 있습니다. Pullrun은 이러한 계층들을 하나의 런타임으로 통합합니다.

핵심 차별점:

  • 동일한 이미지로 컨테이너와 VM 생성: 별도의 VM 빌드 과정이나 별도의 VM 이미지 형식이 필요 없습니다.
  • 콘텐츠 주소 지정 DAG 스토어: 제로 카피(zero-copy) mmap 읽기를 지원하며, 콘텐츠 해시에 의해 중복이 제거되고 모든 노드에서 바이트 단위로 동일합니다.
  • P2P 이미지 분산: 클러스터 당 한 번의 레지스트리 풀만 발생하며, 나머지는 LAN 속도로 P2P 동기화됩니다.
  • 가벼운 용량: 약 14MB CLI + 6MB 런타임 데몬(스트리핑 적용). 기본적으로 데몬이 필요하지 않으며(CLI 전용 pullrun run), 백그라운드 서비스를 위한 선택적 데몬을 제공합니다.

또한 포함된 기능: 쿠버네티스 CRI 심(베타), Docker Compose 지원, AI 에이전트용 MCP 서버, 정책 엔진(Cosign, SBOM, seccomp), P2P 동기화 계층 및 AES-256-GCM 암호화된 시크릿이 모두 동일한 바이너리에 포함되어 있습니다.


💻 코드 예시 (실행 환경별 속도)

  • Apple Silicon VM (macOS 기본): 3초 소요
  • Firecracker 마이크로VM (Linux): 400밀리초 소요
  • 컨테이너 (Linux): 400밀리초 소요
  • Windows WSL2: 동일한 이미지, 동일한 명령어, 동일한 스토어 사용

📦 설치 방법

원 명령어로 모든 플랫폼 설치: curl -fsSL https://github.com/pullrun/pullrun/raw/main/install.sh | bash

플랫폼별 제공 사항:

  • macOS: brew tap pullrun/tap && brew install pullrun -> Xcode 없이 네이티브 바이너리 제공
  • Linux: APT 패키지 또는 직접 다운로드, systemd 서비스 지원 (컨테이너를 위해 runc 필요, VM을 위해 /dev/kvm 필요)
  • Windows: pullrun.exe + WSL2 자동 프로비저닝, runc + Firecracker 지원

🚀 빠른 시작 (Quick Start)

# ── 모든 OCI 이미지 풀(Pull) ──
pullrun pull alpine:3.18

# ── 컨테이너로 실행 (Linux) ──
pullrun run alpine:3.18 --cmd "echo" --cmd "hello pullrun"

# ── 마이크로VM으로 실행 (macOS/Linux) ──
pullrun run alpine:3.18 --backend vm --cmd "echo" --cmd "hello" --attach -t

# ── 분리(detach)가 지원되는 인터랙티브 쉘 ──
pullrun run alpine:3.18 --tty --attach --cmd /bin/sh # Ctrl-P Ctrl-Q 로 분리 (워크로드는 계속 실행됨)

# ── 백그라운드 워크로드 ──
pullrun run alpine:3.18 --cmd /bin/sleep --cmd 3600

# ── 빌드 및 푸시 (Docker 데몬 불필요) ──
pullrun build ./Dockerfile . -t myapp:latest
pullrun build ./Dockerfile . -t myapp:latest --platform linux/amd64,linux/arm64
pullrun push <root-digest> ghcr.io/myorg/myimg:latest

# ── 폐쇄망을 위한 내보내기/가져오기 ──
pullrun save <digest> -o myimage.tar
pullrun load -i myimage.tar

# ── Compose (별도 바이너리) ──
pullrun-compose up -f myapp/compose.yml
원문 보기
원문 보기 (영어)
Pullrun One OCI image. Any execution target. CLI + runtime under 25 MB. Run the same OCI image as a container, Firecracker microVM, Apple Silicon VM, Kubernetes workload, or AI agent task. No daemon required. No overlayfs. No separate VM images. ⚡ 30-Second Install • 🚀 Quick Start • 🏗️ Architecture • 🎯 Why Pullrun? • 🗺️ Feature Map • ☸️ Kubernetes • 🤖 AI Agents ⚡ What is Pullrun? Pullrun runs the same OCI image as a container or a VM. It stores layers in a content-addressed DAG (no overlayfs), syncs blocks peer-to-peer, and ships as a ~14 MB CLI + ~6 MB runtime daemon. Why this matters: Modern infrastructure uses too many execution engines — Docker for dev, containerd for production, Firecracker for isolation, CRI for Kubernetes, MCP agents for AI. Each has its own image format, storage, and operational model — even though they all run the same OCI images. Pullrun collapses these layers into one runtime. Key differentiators: Containers and VMs from the same image — no separate VM build step, no separate VM image format Content-addressed DAG store — zero-copy mmap reads, deduplicated by content hash, byte-identical across every node P2P image distribution — one registry pull per cluster, rest sync peer-to-peer at LAN speed ~14 MB CLI + ~6 MB runtime daemon (stripped) — no daemon required by default (CLI-only pullrun run ), optional daemon for background services Also included: Kubernetes CRI shim (beta), Docker Compose support, MCP server for AI agents, policy engine (Cosign, SBOM, seccomp), P2P sync layer, and AES-256-GCM encrypted secrets — all in the same binary. # Apple Silicon VM (macOS default) — 3 s pullrun run alpine:3.18 --cmd " echo " --cmd " hello pullrun " --attach -t # Firecracker microVM (Linux) — 400 ms pullrun run alpine:3.18 --backend vm --cmd " echo " --cmd " hello pullrun " --attach -t # Container (Linux) — 400 ms pullrun run alpine:3.18 --cmd " echo " --cmd " hello pullrun " # Windows WSL2 — same image, same command, same store pullrun.exe run alpine:3.18 --cmd " echo " --cmd " hello pullrun " 📦 Install # One command, any platform curl -fsSL https://github.com/pullrun/pullrun/raw/main/install.sh | bash Platform What you get macOS brew tap pullrun/tap && brew install pullrun → native binary, no Xcode Linux APT package or direct download, systemd service (requires runc for containers, /dev/kvm for VMs) Windows pullrun.exe + WSL2 auto-provisioning, runc + Firecracker Manual install options # macOS (Homebrew — pre-built, no build deps) brew tap pullrun/tap && brew install pullrun # From source make build && export PATH= " $PWD /bin: $PATH " # Cross-compile Windows CLI cd cli/pullrun && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o pullrun.exe . 🚀 Quick Start # ── Pull any OCI image ────────────────────────────────────────── pullrun pull alpine:3.18 # 968 ms — benchmark script at hack/bench.sh # ── Run as a container (Linux) ───────────────────────────────── pullrun run alpine:3.18 --cmd " echo " --cmd " hello pullrun " # ── Run as a microVM (macOS/Linux) ───────────────────────────── pullrun run alpine:3.18 --backend vm --cmd " echo " --cmd " hello " --attach -t # ── Interactive shell with detach ────────────────────────────── pullrun run alpine:3.18 --tty --attach --cmd /bin/sh # Ctrl-P Ctrl-Q → detach (workload keeps running) # pullrun exec <id> -t /bin/sh → re-attach # ── Background workload ──────────────────────────────────────── pullrun run alpine:3.18 --cmd /bin/sleep --cmd 3600 # ── Build & push (no Docker daemon needed) ───────────────────── pullrun build ./Dockerfile . -t myapp:latest pullrun build ./Dockerfile . -t myapp:latest --platform linux/amd64,linux/arm64 pullrun push < root-digest > ghcr.io/myorg/myimg:latest # ── Export/import for air-gapped ─────────────────────────────── pullrun save < digest > -o myimage.tar pullrun load -i myimage.tar # ── Compose (separate binary) ──────────────────────────────── pullrun-compose up -f myapp/compose.yml # containers (default on Linux) pullrun-compose up -f myapp/compose.yml --backend vm # same compose, VM isolation pullrun-compose logs -f pullrun-compose down # ── Lifecycle ────────────────────────────────────────────────── pullrun list # all workloads (pending/running/exited) pullrun stop < id > pullrun exec < id > /bin/echo hello # Re-attach to a detached workload (allocates a fresh PTY) pullrun exec < id > -t -- /bin/sh # Or, for bidirectional I/O streaming without a new shell: pullrun workload run < id > # ── Image management ────────────────────────────────────────── pullrun images # list pulled images in local DAG store pullrun rmi alpine:3.18 # remove an image, cascade-delete unreachable layers pullrun rmi sha256:abc... # also works by digest # ── GC (DAG store garbage collection) ───────────────────────── pullrun gc # dry-run — report what would be deleted pullrun gc --apply # actually delete unreachable nodes pullrun gc --apply --force # bypass 90% safety guard # ── Events & Stats ───────────────────────────────────────────── pullrun events --types=WORKLOAD_STARTED,POLICY_DENIED pullrun stats < id > # ── Network ──────────────────────────────────────────────────── pullrun network create my-net --subnet 10.43.1.0/24 pullrun run alpine:3.18 --net bridge # ── Secrets ──────────────────────────────────────────────────── pullrun secret create db_password - # from stdin pullrun run myapp:latest --secret db_password # ── Configs ──────────────────────────────────────────────────── pullrun config create nginx.conf ./nginx.conf # from file pullrun config create nginx.conf - # from stdin pullrun run nginx:latest --config nginx.conf # ── Diff & Inspect ───────────────────────────────────────────── pullrun diff < id > pullrun inspect < id > pullrun commit < id > myapp:snapshot # ── Login ────────────────────────────────────────────────────── pullrun login ghcr.io pullrun logout # ── MCP (AI agents) ──────────────────────────────────────────── pullrun mcp # stdio mode (for opencode, Claude Code) pullrun mcp --sse :8080 # SSE mode (for remote agents) # ── P2P sync ─────────────────────────────────────────────────── pullrun-runtime daemon --sync-addr 0.0.0.0:9500 🎯 Why Pullrun? 🏗️ Architecture is Everything Docker's overlayfs store is a filesystem overlay — CVEs in overlayfs (CVE-2023-0386, CVE-2023-32629) can let a container escape to the host. Pullrun's content-addressed DAG store stores layers as-is , verified by content hash. No overlayfs, no escape. Pullrun also mitigates shared-kernel cross-container risks that aren't specific to any storage driver — for example, kernel page-cache bugs like CVE-2026-31431 ("Copy Fail") affect all containers sharing a host kernel, regardless of the storage backend. Per-VM kernel isolation (Firecracker, Apple Virtualization) fully contains these. The DAG store makes the VM path zero-cost: the same image, no separate VM build step. Pullrun is also rootless by default — no sudo needed, no daemon listening on a TCP socket, no attack surface from a central dockerd. 🔥 The Only Runtime with Containers + VMs from the Same Image Backend Isolation Pullrun Docker Podman Linux containers (runc) Process ✅ ✅ ✅ Firecracker microVMs Per-VM kernel ✅ ❌ ❌ Apple Silicon VMs Per-VM kernel ✅ ❌ ❌ Windows WSL2 containers Process ✅ ✅ ❌ Windows Firecracker VMs Per-VM kernel ✅ ❌ ❌ Same image, any isolation level. No separate VM image build step. The OCI manifest IS the VM rootfs. 🧬 Content-Addressed DAG Store Pullrun's store is built on rkyv + mmap . OCI layers are stored once, deduplicated by content hash, and mmap() 'd directly — no tar extraction, no overlayfs, no dockerd process owning the data. A sha256: digest is globally consistent. Every node that has pulled alpine:3.18 stores byte-identical files on disk. This makes P2P sync trivial: blocks are verified by content hash, transferred delta-only, and deduplicated across the entire cluster. 📊 By the Numbers Metric Pullrun Docker First alpine:3.18 pull 968 ms ~2 s Container run latency ~400 ms ~800