One-command development sandboxes on Firecracker microVMs. https://git.thaloco.com/thaloco/banger/
Find a file
Thales Maciel ff51b7ce21
workspace.export: add base_commit to capture worker git commits
Without base_commit, export diffs against the current guest HEAD.
If the worker ran git commit inside the VM, HEAD advanced and the
diff came back empty — committed work was silently lost.

With base_commit set to the head_commit from workspace.prepare,
the diff uses that fixed point instead. After git add -A the index
holds the full working state, so git diff --cached <base_commit>
captures everything: committed deltas (HEAD moved past base) and
any uncommitted changes on top, in one patch, applied with the
same git apply flow.

- WorkspaceExportParams gains base_commit
- WorkspaceExportResult echoes back the ref actually used
- CLI gains --base-commit flag
- Tests assert scripts use the caller-supplied ref and that
  omitting it falls back to HEAD
2026-04-14 16:13:05 -03:00
cmd Stamp shared build metadata into banger binaries 2026-03-22 17:14:06 -03:00
docs/reference Reorganize the source checkout layout 2026-03-21 17:22:57 -03:00
examples Rename experimental Void image to void 2026-04-01 20:15:28 -03:00
internal workspace.export: add base_commit to capture worker git commits 2026-04-14 16:13:05 -03:00
scripts Install claude and pi through mise 2026-04-13 18:29:02 -03:00
.gitignore Reorganize the source checkout layout 2026-03-21 17:22:57 -03:00
AGENTS.md workspace.export: add base_commit to capture worker git commits 2026-04-14 16:13:05 -03:00
go.mod Remove the banger TUI 2026-03-19 22:56:16 -03:00
go.sum Remove the banger TUI 2026-03-19 22:56:16 -03:00
Makefile Rename experimental Void image to void 2026-04-01 20:15:28 -03:00
README.md Add guest sessions and agent VM defaults 2026-04-12 23:48:42 -03:00

banger

banger manages Firecracker development VMs with a local daemon, managed image artifacts, and a localhost web UI.

Requirements

  • Linux with /dev/kvm
  • sudo
  • Firecracker installed on PATH, or firecracker_bin set in config
  • The usual host tools checked by ./build/bin/banger doctor

banger now owns complete managed image sets. A managed image includes:

  • rootfs
  • optional work-seed
  • kernel
  • optional initrd
  • optional modules

There is no runtime bundle anymore.

Build

make build

This writes:

  • ./build/bin/banger
  • ./build/bin/bangerd
  • ./build/bin/banger-vsock-agent

Install

make install

That installs:

  • banger
  • bangerd
  • the banger-vsock-agent companion helper under ../lib/banger/

Config

Config lives at ~/.config/banger/config.toml.

Supported keys:

  • log_level
  • web_listen_addr
  • firecracker_bin
  • ssh_key_path
  • default_image_name
  • auto_stop_stale_after
  • stats_poll_interval
  • metrics_poll_interval
  • bridge_name
  • bridge_ip
  • cidr
  • tap_pool_size
  • default_dns

If ssh_key_path is unset, banger creates and uses:

  • ~/.config/banger/ssh/id_ed25519

default_image_name now only means “use this registered image when vm create omits --image”. The daemon does not auto-register images from host paths.

Core Workflow

Check the host:

./build/bin/banger doctor

Register an existing host-side image stack:

./build/bin/banger image register \
  --name base \
  --rootfs /abs/path/rootfs.ext4 \
  --kernel /abs/path/vmlinux \
  --initrd /abs/path/initrd.img \
  --modules /abs/path/modules

Build a managed image from an existing registered image:

./build/bin/banger image build \
  --name devbox \
  --from-image base \
  --docker

Promote an unmanaged image into daemon-owned managed artifacts:

./build/bin/banger image promote base

Create and use a VM:

./build/bin/banger vm create --image devbox --name testbox
./build/bin/banger vm ssh testbox
./build/bin/banger vm stop testbox

vm create stays synchronous by default, but on a TTY it now shows live progress until the VM is fully ready.

Start a repo-backed VM session:

./build/bin/banger vm run
./build/bin/banger vm run ../some-repo --branch feature/alpine --from HEAD

vm run resolves the enclosing git repository, creates a VM, copies a git checkout plus current tracked and untracked non-ignored files into /root/repo, starts a best-effort guest tooling bootstrap that only uses mise, prints next-step commands, and exits. It does not auto-attach opencode anymore. The bootstrap runs asynchronously and logs its output inside the guest.

After vm run, use one of:

./build/bin/banger vm ssh <vm-name>
opencode attach http://<vm-name>.vm:4096 --dir /root/repo
./build/bin/banger vm acp <vm-name>
./build/bin/banger vm ssh <vm-name> -- "cd /root/repo && claude"
./build/bin/banger vm ssh <vm-name> -- "cd /root/repo && pi"

For ACP-aware host tools, ./build/bin/banger vm acp <vm-name> bridges stdio to guest opencode acp over SSH. It uses /root/repo when that checkout exists, otherwise /root, and --cwd lets you override the guest working directory explicitly.

If you want reusable orchestration primitives instead of the vm run convenience flow, use the daemon-backed workspace and session commands directly:

./build/bin/banger vm workspace prepare <vm-name>
./build/bin/banger vm workspace prepare <vm-name> ../other-repo --guest-path /root/repo --readonly
./build/bin/banger vm session start <vm-name> --name planner --cwd /root/repo --stdin-mode pipe -- pi --mode rpc --no-session
./build/bin/banger vm session list <vm-name>
./build/bin/banger vm session attach <vm-name> planner
./build/bin/banger vm session logs <vm-name> planner --stream stderr
./build/bin/banger vm session stop <vm-name> planner

vm workspace prepare materializes a local git checkout into a running VM. The default guest path is /root/repo and the default mode is a shallow metadata copy plus tracked and untracked non-ignored overlay. Repositories with git submodules must use --mode full_copy; the metadata-based modes still reject them.

vm session start creates a daemon-managed long-lived guest command. The daemon preflights that the requested guest cwd exists and that the main command, plus any repeated --require-command entries, exist in guest PATH before launch. Use --stdin-mode pipe when you need live attach; otherwise use the default detached mode and inspect sessions with list, show, logs, stop, and kill.

vm session attach is currently exclusive and same-host only. The daemon exposes a local Unix socket bridge using stdio_mux_v1, so only one active attach is allowed at a time. Pipe-mode sessions keep enough guest-side state for the daemon to rebuild that bridge after a daemon restart.

Web UI

bangerd serves a local web UI by default at:

  • http://127.0.0.1:7777

See the effective URL with:

./build/bin/banger daemon status

Disable it with:

web_listen_addr = ""

Guest Services

Provisioned glibc-backed images include:

  • banger-vsock-agent
  • guest networking bootstrap
  • mise
  • opencode
  • claude
  • pi
  • a default guest opencode service on 0.0.0.0:4096

Alpine currently remains opencode-only.

If these host auth files exist, banger syncs them into the guest on VM start:

  • ~/.local/share/opencode/auth.json -> /root/.local/share/opencode/auth.json
  • ~/.claude/.credentials.json -> /root/.claude/.credentials.json
  • ~/.pi/agent/auth.json -> /root/.pi/agent/auth.json

Changes on the host take effect after the VM is restarted. Session/history directories are not copied.

From the host:

./build/bin/banger vm ports testbox
opencode attach http://<guest-ip>:4096

Manual Helpers

The shell helpers are now explicit manual workflows under ./build/manual.

Rebuild a Debian-style manual rootfs:

make rootfs ARGS='--base-rootfs /abs/path/rootfs.ext4 --kernel /abs/path/vmlinux --initrd /abs/path/initrd.img --modules /abs/path/modules'

The output lands in:

  • ./build/manual/rootfs-docker.ext4
  • ./build/manual/rootfs-docker.work-seed.ext4

Experimental Void Flow

Stage a Void kernel:

make void-kernel

Build the experimental Void rootfs:

make rootfs-void

Register it:

make void-register

That flow uses:

  • ./build/manual/void-kernel/
  • ./build/manual/rootfs-void.ext4
  • ./build/manual/rootfs-void.work-seed.ext4

Experimental Alpine Flow

Stage an Alpine virt kernel:

make alpine-kernel

Build the experimental Alpine rootfs:

make rootfs-alpine

Register it:

make alpine-register

Create a VM from it:

./build/bin/banger vm create --image alpine --name alpine-dev

That flow uses:

  • ./build/manual/alpine-kernel/
  • ./build/manual/rootfs-alpine.ext4
  • ./build/manual/rootfs-alpine.work-seed.ext4

The experimental Alpine flow stages a pinned Alpine release by default. Override that pin with ALPINE_RELEASE=... when running the make alpine-kernel and make rootfs-alpine helpers if you need a different patch release.

Alpine support currently applies to the explicit register-and-run flow above. The generic banger image build --from-image ... path remains Debian/systemd- oriented and should not be treated as an Alpine image builder.

Notes

  • Firecracker is resolved from PATH by default.
  • Managed image delete removes the daemon-owned artifact dir.
  • The companion vsock helper is internal to the install/build layout, not a user-configured runtime path.