Skip to content

TimLai666/chefer

Repository files navigation

Chefer — Cook Your Containers into Delicious Apps

CI Native Linux E2E Security audit Release License

For developers, Docker is a friendly and convenient way to package applications. However, for end users, asking them to install Docker, pull images, configure networks, and mount volumes just to run an app is simply unrealistic.

Chefer was built to solve this pain point. It combines multiple Docker images in a Docker Compose-like way (which we call an "AppCipe"), then packages them into a single standalone executable that runs without Docker or any container engine — users just download and run.

Chefer turns container app delivery from "Please install Docker" into "Just double-click and run."

With a simple AppCipe recipe (appcipe.yml), you can "cook" your containerized application into a portable single-file app, making container technology truly zero-barrier for end users.

In one line: write an appcipe.yml, point it at your images, and chefer build produces a single executable your users just double-click — no Docker, no Chefer, nothing to install. The packaged app's services share an internal network, persist data, and you declare which ports are proxied to the host. It runs on Linux and Windows today (Windows uses WSL2 when present, otherwise the new WHP backend boots a tiny Linux micro-VM via the Windows Hypervisor Platform — no WSL required); macOS packaging works with execution validation in progress — so it's moving toward "write once, run anywhere", not there on every OS yet. (Honesty note for v1: the internal network is not yet host-isolated on the shared mode — undeclared ports can still be reached from the host, see Roadmap.)

Platform Support

chefer build runs on all three host OSes and can cross-package for any of the six output targets given a kit. What follows is how a packaged app behaves at run time, per host OS, feature by feature. Status reflects the current state of the project — see Known Limitations for the caveat behind every ⚠️.

Capability Linux Windows macOS
Run the single-file app ✅ rootless user namespaces, zero dependencies ✅ WSL2 — a minimal Chefer-dedicated distro is auto-provisioned for the run and best-effort removed when the app exits; or ✅ WHP (no WSL needed) — boots a bundle-embedded Linux micro-VM via the Windows Hypervisor Platform (validated on real hardware) ✅ Virtualization.framework micro-VM — validated on real Apple Silicon, enabled by default (Intel shares the same code path but its VZ boot is unverified on real hardware)
Backend namespaces (in-process guest-agent) wsl2 (preferred when present) → whp (no-WSL micro-VM; virtio-blk + virtio-net over MMIO) vz (Linux appliance + musl guest-agent)
Multi-service apps (depends_on topo order + optional health checks) ✅ verified
Data persistence (persist_path → host dir, survives restarts) ✅ verified ✅ verified (virtiofs write-back; counter survives restarts)
Internal networking (services reach each other via 127.0.0.1:<port>) ✅ verified
Host port mapping — TCP ("host:guest", host≠guest proxied) ✅ verified ✅ verified — the vz backend relays 127.0.0.1:host → <vm_ip>:guest itself (the cross-platform runtime proxy is skipped on macOS)
Host port mapping — UDP ✅ verified — Chefer relays via the VM IP + an in-VM eth0→loopback bridge (WSL2's own forwarding is TCP-only) ✅ verified (same VM-IP relay + in-VM bridge)
GUI services ✅ X11 / Wayland socket passthrough ✅ WSL2 via WSLg (gui-demo); ✅ WHP (no WSL) — bundle-embedded cage compositor on a virtio-gpu scanout shown in a resizable native window, with keyboard/mouse + bidirectional clipboard (text validated bidirectionally on real hardware; PNG images via the PNG format / CF_DIB, host read/convert path verified via --clip-selftest; resizing the window now triggers a true guest re-modeset: the in-guest resize watcher follows the virtio-gpu display change (drm hotplug → re-probe → wlr-output-management; needs the cage ≥ 0.2.0 GUI overlay) — the chain is verified end-to-end on the vz backend, WHP re-validation pending; older overlays fall back to scaling the picture) ✅ validated on real Apple Silicon — VZVirtualMachineView window + the same in-guest cage path as WHP (compositor up 8/8 boots); clipboard verified (text both ways; PNG guest→host byte-exact, host→guest wire-confirmed). Resolution: the initial guest mode matches the window's pixel size (Retina 2× backing; CHEFER_VZ_GUI_SIZE honored — verified 1280×800→2560×1600 and 1024×600→2048×1200), and live resize re-modesets the guest with dynamic resolution opted in (CHEFER_VZ_DYNAMIC_RESOLUTION=1, macOS 14+, cage ≥ 0.2.0 overlay — verified on real hardware: xdpyinfo followed 2560×1600 → 2200×1400); true dynamic resolution is now the default on macOS 14+ — the guest re-modesets on window resize and applies the host's HiDPI output scale (pushed via kernel cmdline chefer.gui_scale) so cursor/UI sizes stay correct on Retina; verified on real hardware including hand-checks (cursor size, pointer mapping, upscale quality; xdpyinfo reports logical points 1280×800 → 1100×700 across a live resize). CHEFER_VZ_DYNAMIC_RESOLUTION=0 (or macOS 13) falls back to view-scaling. Interactive HID + close-window verified on real hardware (driven through the macOS window: xev in the guest logged pointer motion, button press/release, and faithful distinct key events — number-row keycodes 10/11/12/13; letter keys are intercepted by whatever macOS input method is active, a host-side artifact, not the VZ path; the red close button tears the app down with a clean exit 0, no orphan VM). Caveat: the clipboard channel is sometimes silently delayed — suspected macOS Local Network privacy gating of the ad-hoc-signed helper
crash: fail_fast (any non-zero exit tears down the app, code propagated) ✅ verified ✅ verified (exit 7 propagated; interface-service exit-0 teardown too)
Data-dir migration (old_names)
Official chown/gosu images (redis, postgres, …) ✅ as root (no userns); ✅ rootless via /etc/subuid + newuidmap (falls back to single-uid without them) ✅ verified — distro runs as real root, runs official redis/postgres as-is ✅ (same real-root guest path as WHP)
windows/* containers

Legend: ✅ implemented & exercised in CI / on a real machine · ⚠️ works with a documented caveat · ❌ not supported.

Linux containers only — linux/amd64 and linux/arm64. windows/* images are rejected at validation on every host.

Skills — teach your AI agent to write AppCipes

Writing an appcipe.yml by hand means knowing the field reference, the validation rules, and a handful of real-world gotchas (which images need full uid mapping, how internal networking actually works in v1, what app_version does and doesn't do). This repo ships an agent skill that packs all of that into one place: skills/write-appcipe.

If you use an agentic coding tool (Claude Code, Codex, etc.), install the skill so your agent can author and validate recipes for you:

npx skills add TimLai666/chefer/skills

Then just ask your agent to "write an appcipe for these images" — it will follow the field reference, apply the validation rules, sidestep the known gotchas, and run chefer check for you.

Install

Running a Chefer-packaged app needs nothing — the single file is self-contained. You only need to install Chefer if you want to package apps yourself.

Option 1 — One-line installer (recommended)

# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/TimLai666/chefer/main/scripts/install.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/TimLai666/chefer/main/scripts/install.ps1 | iex

The script detects your OS/arch, downloads the matching package from the latest release, verifies its sha256, installs to ~/.chefer (or %LOCALAPPDATA%\chefer), and adds it to your PATH. Open a new terminal, then chefer version. Pin a version with CHEFER_VERSION=<tag> ($env:CHEFER_VERSION on Windows) and change the location with CHEFER_INSTALL_DIR. Update later with chefer upgrade. On macOS, allow the unsigned binary in System Settings → Privacy & Security on first run.

Option 2 — Manual download

Grab chefer_<version>_<target>.zip (Windows) / .tar.gz (Linux/macOS) from the latest release, extract it, and put the chefer executable on your PATH — keep the kit/ folder beside it (the runtimes, guest-agents and macOS appliance it needs to build apps; it's auto-discovered at <exe dir>/kit). One package can cross-package for every target.

Option 3 — From source

git clone https://github.com/TimLai666/chefer && cd chefer
cargo build --release -p chefer-cli          # binary at target/release/chefer-cli

init / check / inspect work straight away. To chefer build / run you also need a kit (prebuilt runtime + guest-agent, plus the appliance for macOS targets) — either copy the kit/ from a release, or build your own (see Building from Source) and point at it with --kit-dir / CHEFER_KIT_DIR. From a source checkout you can also just run cargo run -p chefer-cli -- <command>.

Quick Start

  1. Export your image(s) as tar (both docker save and OCI archives are accepted; multi-arch archives are auto-resolved by each service's platform):

    docker save -o images/app.tar myimage:latest
  2. Generate an AppCipe template:

    chefer init
  3. Edit appcipe.yml — set the app name, point each service's image at its tar, declare ports / env / persistence as needed (see examples/appcipe.yml for a fully-commented reference), then validate:

    chefer check
  4. Build the single-file executable:

    chefer build                                       # for the current platform
    chefer build --target x86_64-pc-windows-msvc       # or cross-package for another one
  5. Distribute the file under dist/<Name>/ — that's it. Users just download and run; no Docker, no installer.

chefer build needs a runtime kit (prebuilt chefer-runtime-<target>, guest-agent-<arch>, and for macOS targets chefer-vmlinuz-<arch> / chefer-initramfs-<arch> appliance files). Every package on the latest GitHub Release ships a complete kit/ containing runtimes for all 6 targets, both musl guest-agent architectures, and the macOS micro-VM appliance — download one package and you can package for every platform. Kit search order: --kit-dir > CHEFER_KIT_DIR > <exe dir>/kit > <exe dir>.

Demos

App + database in one fileexamples/demo is a minimal but complete two-service app: a Python HTTP service that increments a visit counter stored in redis, with the redis data persisted across restarts. It demonstrates internal networking (app reaches db over 127.0.0.1:6379), opt-in persistence (persist_path: /data), and host port mapping (18080:8080) — all in one executable. Its README also documents honestly where v1's networking model stops short (services share one network namespace, so "db not exposed" is not yet true isolation — see Known Limitations).

bash examples/demo/scripts/build-images.sh          # or .ps1 on Windows — needs Docker
cargo run -p chefer-cli -- build examples/demo/appcipe.yml --out dist
./dist/CheferDemo/CheferDemo_<target>                # then curl http://127.0.0.1:18080/

GUI in one fileexamples/gui-demo packages a graphical X11 program (xeyes) with interface_mode: gui. Run the single file and a window appears: on Linux via X11/Wayland socket passthrough, on Windows via WSLg. Because fail_fast tears the app down if the GUI program can't reach the display, "the window shows up and the app keeps running" is itself proof the GUI path works. See its README.

bash examples/gui-demo/scripts/build-images.sh      # or .ps1 on Windows — needs Docker
cargo run -p chefer-cli -- build examples/gui-demo/appcipe.yml --out dist
./dist/CheferGuiDemo/CheferGuiDemo_<target>          # a window with googly eyes appears

CLI Commands

Command Description
chefer init [dir] Generate an appcipe.yml template (never overwrites an existing file).
chefer check [path] [--format pretty|json|yaml] Parse and validate the recipe, print a summary.
chefer build [path] [--out <dir>] [--target <triple>]... [--kit-dir <dir>]... [--zstd-level N] [--no-embed-original] [--dry-run] Package into single-file executable(s); --target may be repeated, defaults to the host target.
chefer run [path] [build options] Build for the host target, then run the artifact immediately (stdio passthrough, exit code propagated).
chefer doctor [--kit-dir <dir>]... Check whether the current machine can build/run Chefer apps; prints PASS/WARN/FAIL diagnostics with actionable fixes, including the Windows WHP fallback host preflight.
chefer inspect <file> Show the footer, app metadata, service details, layer sizes, and embedded companion files of a Chefer single-file executable (no execution, no filesystem extraction).
chefer version Show Chefer and environment version info.
chefer upgrade [--channel stable] [--to <ver>] [--check-only] Self-update from GitHub Releases — replaces both the chefer binary and the whole kit/ together (sha256-verified, with rollback), so the CLI and kit never drift apart.
chefer selfrm [-y] [--clean-wsl] Remove Chefer itself (the chefer binary + its kit/) and clean the installer's PATH entry. Does not touch apps you packaged or their data. Current Windows packaged apps best-effort clean their temporary WSL distro on exit; --clean-wsl additionally removes old chefer-rt-* runtime leftovers.

chefer inspect also shows "Packed by chefer", service layer sizes, ports, mounts, healthcheck presence, and embedded agents/ / vm/ companion files, so you can tell what a single-file app contains without extracting it.

[path] defaults to ./appcipe.yml; a directory argument means <dir>/appcipe.yml.

The AppCipe Format

A Docker Compose-flavored YAML. The essentials:

  • version: "0.1" (required), name (required; also the output file and data-folder name), app_version (display-only metadata — not injected into containers), data_dir, old_names (automatic data-dir migration), crash: fail_fast.
  • Each entry under services: has an image — either a registry reference (image: redis:7.2-alpine, pulled at build time; a pinned tag or @sha256 digest is required — latest/untagged is rejected) or a local image tar (image: ./db.tar, from docker save), or the full source/file/format/platform form (source: image | tar) — plus optional cmd, workdir, env, persist_path, ports ("host:guest[/proto]"), mounts ("<host_path>:<container_path>"), interface_mode (gui | terminal | both | none), depends_on, and healthcheck.
  • Persistence is opt-in per service via persist_path; data lives on the host under {data_dir or platform default}/data/{service}/ and survives restarts.
  • Inter-service networking is implicit: services in an app share the app network, so a service reaches another at 127.0.0.1:<port>. Only ports: entries get a host→guest proxy. The app-level network: field (bridge default | internal | shared) controls host isolation — bridge/internal give the app its own netns so undeclared ports are unreachable from the host; shared is the old non-isolated behavior.

See examples/appcipe.yml for the fully-commented reference, examples/appcipe_simple.yml for the minimal one, and the write-appcipe skill for the full field reference, validation rules, and gotchas.

How It Works

At build time Chefer parses each image tar and stores its original layers (zstd-compressed) plus a manifest.json into a bundle, then appends zstd(tar(bundle)) and an 80-byte footer to a prebuilt chefer-runtime binary — producing one executable. At run time that executable verifies and extracts the bundle, then hands it to a platform backend in which a small guest-agent assembles the rootfs from the layers (applying OCI whiteouts) and starts the services. The rootfs is always assembled inside a Linux environment, so symlinks, permissions and case-sensitivity stay intact even when the file was built on Windows.

appcipe.yml + image tars
        │
        ▼
chefer build
  ├─ pack:      image tars ──> bundle/ (zstd layers + manifest.json)
  └─ assemble:  [chefer-runtime][zstd(tar(bundle))][footer]
        │
        ▼
single executable ── user double-clicks ──> chefer-runtime
  ├─ verify sha256, extract bundle to temp
  ├─ resolve data dir (+ old_names migration), start port proxies
  └─ pick platform backend:
       Linux   → rootless namespaces (in-process)
       Windows → WSL2 (auto-provisioned minimal distro) → WHP (no-WSL Linux micro-VM via Windows Hypervisor Platform: virtio-blk bundle/data, virtio-net TCP/UDP port forwarding, outbound NAT)
       macOS   → Virtualization.framework micro-VM (validated on real Apple Silicon; enabled by default)
            │
            ▼
       guest-agent: assemble rootfs from layers (whiteouts),
                    bind persist dirs & mounts,
                    start services in depends_on order (health checks gate readiness),
                    fail-fast supervision

Building from Source

cargo build                 # whole workspace (Windows / Linux / macOS)
cargo test                  # unit + integration tests (no Docker needed)

To build the static guest-agent (needed in the kit when packaging for Windows/macOS targets):

rustup target add x86_64-unknown-linux-musl
cargo build -p chefer-runtime --release
cargo build -p guest-agent --target x86_64-unknown-linux-musl --release

The musl targets are already configured to link with rust-lld (see .cargo/config.toml), so they build on any host without a musl C toolchain. To use self-built binaries as a kit, place them in a directory as chefer-runtime-<target-triple>[.exe] and guest-agent-<arch> and pass it via --kit-dir (or CHEFER_KIT_DIR).

To build and validate the macOS micro-VM appliance on Linux:

CHEFER_LINUX_REF=v6.6.32 bash scripts/build-appliance.sh --arch x86_64 --out dist/appliance
CHEFER_LINUX_REF=v6.6.32 bash scripts/qemu-e2e.sh

scripts/qemu-e2e.sh boots the appliance with QEMU + virtiofs, runs a real Chefer bundle through the musl guest-agent, and verifies namespaces, persistence, fail-fast exit code propagation, and host≠guest TCP forwarding. The actual macOS Virtualization.framework backend still must be validated on a physical Mac; GitHub-hosted macOS runners cannot boot nested VZ guests.

To validate the Windows WSL2 runtime lifecycle on a Windows machine with WSL2 + Docker:

.\scripts\windows-wsl-cleanup-e2e.ps1

The script builds a real Windows single-file app, runs it once with CHEFER_KEEP_WSL_DISTRO=1 to prove the expected chefer-rt-* distro can be retained, unregisters it, then runs normally and verifies the runtime unregisters the distro after app exit. If that distro name already exists before the test, pass -CleanExisting only when you are sure it is not in use.

Known Limitations

Honest list of what doesn't work (yet):

  • macOS VZ execution still needs physical-Mac validation. Packaging on/for macOS can embed the Linux appliance and the guest path is validated on Linux+QEMU, but GitHub-hosted macOS runners cannot boot a Virtualization.framework guest, so the actual VZ boot path is unverified on real hardware. The vz backend reports itself unavailable until then.
  • Per-app network isolation is the default (network: bridge). By default an app gets its own network namespace where only the ports: you declare are reachable from the host and undeclared ports are walled off — with outbound NAT (via a bundled pasta). Services still reach each other at 127.0.0.1:<port> inside the app. network: internal is the same but with no outbound; network: shared opts back into the old behavior (all services share one netns, and on Windows WSL2's wslrelay mirrors any listening port to the host, so undeclared ports stay reachable). Validated on native Linux (amd64+arm64, rootless) and on real WSL2 (isolation and bridge outbound). See examples/demo/README.md for a measured demonstration of the shared gap.
  • UDP on Windows uses a Chefer-managed relay (works; minor caveat). WSL2's own localhost forwarding is TCP-only, so Chefer forwards UDP to the VM's IP and runs an in-VM eth0→loopback bridge so even loopback-bound UDP services are reachable. The bridge binds after a short grace so services that bind 0.0.0.0 win the port (and are hit directly); a UDP service that binds 0.0.0.0 slower than that grace could rarely lose the port to the bridge — a clear, retryable bind error if it happens.
  • Official chown/gosu images and rootless Linux: images whose entrypoint chowns to / gosus a dedicated service uid (e.g. official redis, postgres) work on WSL2, the macOS VM, and native Linux run as root — there Chefer runs services as real root (no user namespace), so chown/gosu to any uid succeeds (verified end-to-end with the official redis image on WSL2). On the native Linux rootless path (running the single file as a non-root user), Chefer uses /etc/subuid//etc/subgid delegation via newuidmap/newgidmap when available (the same mechanism as rootless Podman) to map a full uid range into the container — chown/gosu images then work rootless too (verified end-to-end with the official redis image as a non-root user). Without delegation (no uidmap package, or no subuid ranges for the user), it falls back to the kernel's single-uid self-map and such images may fail; install uidmap and ensure a /etc/subuid entry (most distros create one per user by default) to lift that.
  • GUI support: native Linux passes through X11/Wayland sockets; Windows WSL2 relies on WSLg; the Windows WHP path (no WSL) runs a bundle-embedded cage Wayland compositor (with Xwayland for X11 apps) on a virtio-gpu scanout, shown in a native window with keyboard/mouse and a bidirectional clipboard (text plus PNG images, interoperating with both the registered PNG clipboard format and CF_DIB). GUI is always software-rendered (llvmpipe, no GPU acceleration — see below).
  • GPU access is opt-in (gpu: true) and platform-bounded; validated on both WSL2 and native NVIDIA Linux. By default a container gets no GPU device nodes (its /dev is a fixed allowlist), so apps are CPU-only unless you opt in. Setting gpu: true on a service binds the host's GPU device nodes into that service's container — /dev/dri + /dev/nvidia* on native Linux, and /dev/dxg + the host /usr/lib/wsl/lib and /usr/lib/wsl/drivers driver libs on the Windows WSL2 backend (which makes CUDA/DirectML/OpenCL/Vulkan work out of the box, since WSL ships host-version-matched libs; both dirs are needed because the lib copies are loader shims that dlopen the real driver from the drivers store — validated end-to-end running nvidia-smi and a nvcc-built CUDA vectorAdd in-container on a GeForce GT 1030). It is a clear startup error on the non-WSL Windows (WHP) micro-VM and the macOS VM — a bare WHP/VZ VM can't tap Microsoft/Apple GPU paravirtualization (that stack is tied to WSL2/Sandbox/Hyper-V-with-GPU-P), and Virtualization.framework's virtio-gpu is display-only. There is no path to the Apple Neural Engine / NPUs from a Linux guest. On native Linux, host-driver-version-matched userspace lib injection (à la the NVIDIA Container Toolkit) is implemented and validated on real NVIDIA hardware — Chefer reads the driver version from /proc/driver/nvidia/version and binds the matching NVIDIA driver libs (libcuda/libnvidia-*/libnvcuvid/libnvoptix/GL, matching nvidia-container-cli list) into /run/chefer-nvidia (versioned + real DT_SONAME + .so) plus LD_LIBRARY_PATH; verified on a GeForce RTX 4070 (driver 570.211.01) by running a nvcc-built CUDA vectorAdd from a stock nvidia/cuda image that ships no libcuda. WSL2 needs nothing extra. AMD (ROCm) and Intel (oneAPI) need no host lib injection — their userspace ships in the image (rocm/*, intel/oneapi), so Chefer just binds the device nodes: AMD gets /dev/kfd + /dev/dri, Intel gets /dev/dri (the standard containerized-ROCm model). Implemented but not yet hardware-validated — this repo has no AMD/Intel GPU. Net: GPU compute is realistic only on native Linux and WSL2.

GPU support at a glance (gpu: true; native-Linux namespaces and Windows wsl2 backends only — a clear startup error on the WHP micro-VM and macOS VM):

Vendor What Chefer binds Compute that works Hardware-validated?
NVIDIA native: /dev/nvidia* + host-driver-matched lib injection (/run/chefer-nvidia) + nvidia-smi; WSL2: /dev/dxg + /usr/lib/wsl/lib + /usr/lib/wsl/drivers CUDA, ML frameworks (PyTorch), NVENC/NVDEC video, nvidia-smi yes — native RTX 4070 (CUDA vectorAdd + PyTorch 2.4 GPU training + h264_nvenc + nvidia-smi) & WSL2 GT 1030 (CUDA vectorAdd + nvidia-smi)
AMD /dev/kfd + /dev/dri; ROCm/HIP userspace from the image ROCm/HIP, OpenCL ⚠️ no — implemented, no AMD GPU to test on
Intel /dev/dri; oneAPI/Level-Zero userspace from the image oneAPI/Level-Zero, OpenCL ⚠️ no — implemented, no native-Linux Intel GPU to test on

Per-service card selection: gpu: true (or the Docker-style alias gpu: all) gives a service all GPUs; gpu: [0, 2] binds only /dev/nvidia0 + /dev/nvidia2 into that service (hard isolation, like NVIDIA_VISIBLE_DEVICES). Card-index selection is native-NVIDIA only; on WSL2 / AMD / Intel use a per-service env (CUDA_VISIBLE_DEVICES / HIP_VISIBLE_DEVICES / ZE_AFFINITY_MASK) for soft selection. (Index filtering is unit-tested; multi-card isolation is not yet hardware-validated — the validation boxes are single-GPU.)

OpenGL/Vulkan rendering does not work in a headless container for any vendor (NVIDIA's GL/Vulkan userspace needs a real X/graphics environment — even nvidia-container-toolkit fails headless; use the GUI path + software rendering for display). GPU compute (the table above) is unaffected. No path to Apple NPU / CoreML from a Linux guest.

  • Image source is a registry pull, a local tar, or a Dockerfile. image: redis:7.2-alpine (or source: image) pulls from any OCI registry at build time — private registries supported: Chefer uses docker login credentials (plain auths entries in ~/.docker/config.json; external credential helpers/credsStore are not read) or CHEFER_REGISTRY_AUTH=user:pass, falling back to anonymous for public images — selecting the right arch by platform; a pinned tag or @sha256 digest is required (latest/untagged rejected for reproducibility). image: ./db.tar (or source: tar) uses a local docker save / OCI archive. source: dockerfile builds the image at build time using a container builder already on the build machine (docker/podman/nerdctl/Apple container, auto-detected; OrbStack & Docker Desktop are used via their docker CLI) — not reproducible (use source: image pinned to a digest if you need that), and the runtime still needs nothing.
  • Linux containers only (linux/amd64, linux/arm64); windows/* containers are rejected at validation.
  • depends_on supports wait-until-ready health checks: a service with a healthcheck (Docker-style command, run inside the container) gates its dependents until it's healthy; a service without one is treated as ready once spawned (start-order only).
  • At most one service per app may use interface_mode: terminal or both; host ports must be unique across the whole app.
  • On Windows the runtime prefers WSL2, but no longer requires it. When WSL2 is present the wsl2 backend is used (wsl --install once if absent; WSL2 runs on the Hyper-V hypervisor, enabled via the lightweight "Virtual Machine Platform" feature — not the full Hyper-V role, so it works on Windows Home — plus hardware virtualization in BIOS). When WSL2 is absent, the WHP backend boots a bundle-embedded Linux micro-VM via the Windows Hypervisor Platform (needs hardware virtualization + the WHP optional feature) and runs the real bundle with no WSL — virtio-blk bundle/data, persistence, TCP/UDP port forwarding, and outbound NAT — including the default network: bridge mode (per-app netns via the bundled pasta, same as the other backends). GUI apps also work on the WHP path — a bundle-embedded cage compositor renders (software/llvmpipe) to a virtio-gpu scanout shown in a native window, with keyboard/mouse and bidirectional text clipboard. The WHP path is CPU-only for acceleration (no GPU compute/hardware rendering; see above). chefer doctor preflights the WHP API + active-hypervisor state.

Roadmap

  • Host-isolated networking — bridge is now the default. Omitting network: gives an app its own netns where only declared ports: are reachable and undeclared ports are walled off from the host, with outbound NAT via a bundled pasta (like Docker's default). network: internal is the same without outbound; network: shared opts back into the old "everything shares one netns / undeclared ports reachable" behavior. Validated on native Linux (amd64+arm64, rootless) in CI, on real WSL2, and on the real-hardware WHP micro-VM — both internal isolation and bridge outbound.
  • Pull images straight from a registry (like Docker Compose) — image: redis:7.2-alpine and chefer build fetches it (anonymous, public images), no docker save needed. latest/untagged is rejected — a pinned tag or @sha256 digest is required, so a build is reproducible. (Private-registry auth still TODO.)
  • Build from a Dockerfile (source: dockerfile) — chefer build builds the image for you using a container builder already on the build machine (docker/podman/nerdctl, auto-detected), then ingests it through the normal pipeline. Optional context: and build_args:. The runtime still needs nothing. Not reproducible (use source: image pinned to a digest if you need that); if no builder is found you get an actionable error.
  • Windows without WSL2 (WHP) — boots the bundled Linux micro-VM appliance (the same kernel + initramfs + guest-agent the macOS vz backend uses) via the Windows Hypervisor Platform (WHP), as a fallback to the wsl2 backend. Implemented and validated on real hardware: virtio-over-MMIO blk (bundle/data) + net, data persistence across restarts, host↔guest TCP/UDP port forwarding, guest outbound NAT (UDP + TCP) — in all network modes, including the default bridge (the bundled pasta NATs the per-app netns to the VM's eth0, then the helper NATs eth0 to the outside; declared-port forwarding and undeclared-port isolation verified on real hardware too) — and full GUI: a bundle-embedded cage Wayland compositor (+ Xwayland) renders to a virtio-gpu (2D-over-MMIO) scanout shown in a native window at the app's preferred resolution (via EDID), with keyboard/mouse (virtio-input) and a bidirectional clipboard, all validated on real hardware. The clipboard syncs text (validated on real hardware) and now images too — PNG over the wire, interoperating with both the registered PNG clipboard format (browsers/modern apps) and CF_DIB (Paint/Snipping Tool/Office, via a unit-tested PNG↔DIB conversion); text sync is verified bidirectionally on real WHP, and the host image read/convert path (CF_DIB→PNG) is verified via chefer-whp-helper --clip-selftest; the guest-side apply reuses the proven text wl-copy mechanism. Remaining polish: hardware cursor. A software-emulation fallback (bundled QEMU/TCG) could run on machines with no virtualization at all, at a significant speed cost. Trade-off: GUI is software-rendered and this path is CPU-only for GPU acceleration — WSL2's GPU paravirtualization (/dev/dxg) is a Microsoft-integrated stack a raw WHP VMM can't tap, so anyone needing GPU compute must stay on the WSL2 backend.
  • [~] Faster app startuppartly done. The single file now caches its extracted bundle in a content-hashed dir (keyed on the payload sha256): the first run decompresses+verifies once, and subsequent runs skip extraction entirely (no re-open, re-decompress, or re-hash of the payload) — the big win for large images launched repeatedly. (--no-cache forces fresh temp extraction.) The guest-agent's per-service rootfs assembly is cached, decompresses layers in parallel on first assembly (a worker pool decompresses ahead while layers are still applied strictly in order, preserving overlay/whiteout semantics — the pure-Rust musl ruzstd decode is CPU-bound), and on root backends (WSL2 / macOS VM / native-root) uses overlayfs: each layer is extracted once into its own content-addressed read-only lowerdir (shared/deduped across services and images, OCI whiteouts converted to overlay whiteouts), then mounted as an overlay with a per-run writable upper — no merge-copy, instant mount, layers shared. Rootless native Linux keeps the merged path (rootless can't create overlay whiteout devices). Windows WSL distros are now best-effort removed when the app exits, so WSL import remains a per-run cost unless CHEFER_KEEP_WSL_DISTRO=1 is set for local debugging or repeated-launch profiling. GUI apps on the WHP/VZ micro-VM now ship their cage/Xwayland/Mesa overlay as a read-only squashfs that is mounted (via overlayfs) instead of decompressed — the VM is fresh every boot and can't cache across runs, so this removes a ~200 MB per-launch unpack from every GUI start.
  • depends_on health checks (wait-until-ready), not just start order — a service with healthcheck is polled inside its container namespaces until healthy before later services start; services without one are considered ready once spawned.
  • Rootless Linux support for chown/gosu images via newuidmap + /etc/subuid delegation — when the host has the uidmap tools and subuid/subgid ranges for the user (most distros ship both), rootless services get a full uid-range mapping (rootless-Podman-style) and images that chown/gosu to a dedicated uid (official redis/postgres) run as-is; verified end-to-end with the official redis image as a non-root user. Falls back to the old single-uid map when delegation is unavailable. (The root backends — WSL2 / macOS VM / native-root — never needed this.)
  • macOS VZ boot validated on real Apple Silicon (2026-07, Apple Silicon / macOS 26)scripts/vz-smoke.sh fully green on bare metal: VM boot + virtiofs + guest-agent, exit-code propagation, service supervision, host→guest TCP and UDP port relays, multi-service depends_on + in-app 127.0.0.1 networking, persist_path write-back across restarts, and default-bridge outbound (pasta → VZ NAT, DNS included). Real-hardware fixes that came out of it: tar-mode restore on extraction (bundled agents lost their exec bit), an arm64-appliance initramfs that shipped an x86-64 busybox (cross-arch busybox-static now fetched at build), the runtime port proxy double-binding against the vz relay, and Docker-parity /etc/resolv.conf injection into service containers (without it, bridge outbound had connectivity but no DNS). Note: VZ is virtualization, not emulation — guest arch must match host (Apple Silicon → linux/arm64, Intel → linux/amd64). The backend is now enabled by default (the old CHEFER_VZ_EXPERIMENTAL opt-in is gone) — availability() gates on the real preconditions (host arch, embedded appliance, embedded helper, macOS 13+). Intel Macs take the identical code path with an x86_64 appliance, but their VZ boot hasn't been exercised on real hardware (no CI can — a GitHub runner can't boot a VZ guest on either arch).
  • GUI apps on macOSvalidated on real Apple Silicon (2026-07). The in-guest half (kiosk cage + Xwayland on a virtio-gpu scanout) is shared with WHP; chefer-vz-helper --gui shows the VM in an AppKit window via VZVirtualMachineView (1280×800 default, CHEFER_VZ_GUI_SIZE=WxH override) with USB keyboard/absolute-pointer devices. Real-hardware results: the compositor came up 8/8 GUI boots (the WLR_LIBINPUT_NO_DEVICES boot-race fix holds on VZ); X11 clients connect and render; the host↔guest clipboard works (text verified both ways; PNG guest→host byte-exact on NSPasteboard, host→guest wire-confirmed). Resolution: automaticallyReconfiguresDisplay (macOS 14+) does push the window's pixel size to the guest — the initial mode matches the window's Retina backing exactly (a 1280×800-point window yields a 2560×1600 guest mode; CHEFER_VZ_GUI_SIZE=1024x600 yields 2048×1200) — and live resize now re-modesets the guest: cage doesn't follow mode changes by itself, so the guest agent's resize watcher listens for the drm hotplug, re-probes the new preferred mode, and applies it over wlr-output-management (needs the cage ≥ 0.2.0 overlay from the current build-gui-overlay.sh; verified end-to-end on real hardware with CHEFER_VZ_DYNAMIC_RESOLUTION=1: xdpyinfo followed 2560×1600 → 2200×1400). True dynamic resolution is now the default on macOS 14+: the guest re-modesets on window resize and applies the host's HiDPI output scale (the helper pushes the screen's backingScaleFactor via kernel cmdline chefer.gui_scale; the resize watcher sets it at startup and on every re-modeset), so Retina-pixel modes no longer halve Linux UI sizes — verified end-to-end on real hardware (boot output scale -> 2; xdpyinfo logical 1280×800 → 1100×700 across a live resize, never the physical pixel sizes; hand-checks passed: cursor/UI size, pointer mapping, upscale quality). CHEFER_VZ_DYNAMIC_RESOLUTION=0 (or macOS 13, or an old cage <0.2.0 overlay where the guest can't re-modeset) falls back to view-scaling with the aspect lock. Input + close-window verified on real hardware by driving the macOS window directly: xev inside the guest logged pointer motion, button press/release, and faithful distinct key events (number-row keycodes 10/11/12/13 arrived one-for-one) — proving the NSEvent → VZ USB HID → guest evdev path is intact; letter keys get transformed by whatever macOS input source is active (Zhuyin/注音 on the test machine) before reaching the VM view, so that mismapping is host-side, not chefer's. Clicking the red close button ran windowWillClose → vm.stop → exit(0): runtime reported exit code = 0 with no orphan helper/VM. Known caveat: the clipboard channel (helper → <vm_ip>:55381 over VZ NAT) sometimes sits in a silent .waiting state for up to a minute or never connects — suspected macOS Local Network privacy gating of the ad-hoc-signed helper; needs a stable signing identity and a visible hint in the helper. Design in docs/DESIGN.md.
  • GPU access / CUDA computeopt-in gpu: true; validated end-to-end on both WSL2 and native NVIDIA Linux. Set gpu: true on a service and Chefer binds the host's render/compute device nodes into that service's container — on native Linux /dev/nvidia* + /dev/dri; on the Windows WSL2 backend /dev/dxg + a bind of the host /usr/lib/wsl/lib and /usr/lib/wsl/drivers (its host-version-matched libdxcore/libcuda/… ) → CUDA/DirectML/OpenCL/Vulkan work with nothing extra in the image. The same passthrough also covers NVENC/NVDEC video and OpenCL/ROCm compute; GL/Vulkan rendering does not work in a headless container (see the native note below). Validated end-to-end on real WSL2 (GeForce GT 1030) — the container sees /dev/dxg + /usr/lib/wsl/lib + /usr/lib/wsl/drivers, runs nvidia-smi with live telemetry, and executes a nvcc-built CUDA vectorAdd on the GPU (sm_61, 1048576 elems, 0 mismatches); binding /usr/lib/wsl/lib alone is not enough — its libcuda/libnvidia-ml are loader shims that dlopen the real driver from /usr/lib/wsl/drivers, so both must be bound (else nvidia-smi reports "Driver Not Loaded"). /dev/dri is bound when present (absent on some WSL kernels; CUDA uses /dev/dxg). It is a clear startup error on the non-WSL Windows (WHP) micro-VM and the macOS VM — a bare WHP/VZ VM can't tap Microsoft/Apple GPU paravirtualization, so GPU compute stays native-Linux + WSL2 only. Native Linux — host-driver-version-matched userspace lib injection (à la the NVIDIA Container Toolkit) reads the driver version from /proc/driver/nvidia/version and binds the matching NVIDIA driver libs (libcuda/libnvidia-*/libnvcuvid/libnvoptix/GL, matching nvidia-container-cli list) into /run/chefer-nvidia (versioned + real DT_SONAME + .so) plus LD_LIBRARY_PATH. Validated on a GeForce RTX 4070 (driver 570.211.01): a nvcc-built CUDA vectorAdd from a stock nvidia/cuda image that ships no libcuda ran on the GPU (sm_89, 1048576 elems, 0 mismatches), and ffmpeg -c:v h264_nvenc encoded in-container (NVENC, exit 0). OpenGL/Vulkan rendering does not work in a headless container, though: NVIDIA's Vulkan ICD (libGLX_nvidialibnvidia-glcore) needs a real graphics/X environment (it references Xorg's ErrorF and glibc-2.34-removed __malloc_hook), so it falls back to llvmpipe — and docker --gpus all with NVIDIA_DRIVER_CAPABILITIES=all (the nvidia-container-toolkit reference) fails identically on the same headless host, so this is a host/driver limitation, not a Chefer one (Chefer's GUI path renders via X11/Wayland socket passthrough + software rasterization instead). No path to Apple NPU / CoreML from a Linux guest.

About

Pack Docker/OCI images into a single standalone executable that runs without Docker.

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors