Skip to content

Build custom snapshots with Docker on Linux - #89

Merged
mavdol merged 8 commits into
capsulerun:mainfrom
trieloff:dockerfile-snapshots-linux
Aug 21, 2026
Merged

Build custom snapshots with Docker on Linux#89
mavdol merged 8 commits into
capsulerun:mainfrom
trieloff:dockerfile-snapshots-linux

Conversation

@trieloff

Copy link
Copy Markdown
Contributor

Summary

  • use Docker Buildx to export the linux/riscv64 root filesystem on Linux
  • keep Apple's container CLI as the macOS builder
  • rename the builder script to the platform-neutral build-custom-snapshot.sh
  • document the one-time riscv64 binfmt setup for Docker hosts

Stack

Depends on #79. That branch was rebased onto the latest origin/main first; the Linux support is the single top commit 857f55a. Until #79 merges, GitHub's comparison also shows its prerequisite changes.

Verification

  • sh -n scripts/build-custom-snapshot.sh
  • sh -n scripts/aot-snapshot.sh
  • scripts/build-custom-snapshot.sh --help
  • Linux dependency smoke test confirms the script selects Builder: docker and reports missing Docker/tooling cleanly
  • git diff --check

A full snapshot build was not run in the Amp orb because Docker is not installed there.

as-a-bot Bot and others added 7 commits August 20, 2026 14:34
Installed automatically by the as-a-bot app.
Adds scripts/build-snapshot-from-dockerfile.sh: builds the Dockerfile
for linux/riscv64 with Apple's container CLI (BuildKit runs RUN steps
under riscv64 emulation), exports the flattened rootfs with the tar
exporter, and swaps it in for the Alpine minirootfs of
build-default-snapshot.sh — same overlay, same vpod-native boot, same
--snapshot-save capture.

Details worth knowing:
- BuildKit's tar exporter rewrites absolute symlink targets to include
  the per-platform directory (/bin/sh -> /linux_riscv64/bin/busybox);
  the script points them back at the root, otherwise every busybox
  applet dangles and the guest panics on init.
- Python warm-start (shim + pydaemon + pyrunner) is applied only when
  the built image contains python3; other images produce shell-only
  snapshots instead of failing the build.
- Setup markers are matched anchored at line start, and a kernel panic
  during the finalize boot aborts the build instead of shipping a
  panicked machine state.
- Prints a ready-to-paste registry/catalog.json entry (sha256 + size).

Verified end to end with FROM alpine:3.23 + apk add python3 nodejs git
+ COPY: the resumed snapshot answers cat/python3(-shim)/node/git probes
over --snapshot-load --setup.

Signed-off-by: Lars Trieloff <lars@trieloff.net>
Three pieces that fell out of taking a node image through the full
pipeline:

- aot-snapshot.sh gains --workload custom with repeatable --trace-cmd
  steps: images whose hot code is not python/apk (or that do not even
  contain them) trace their own workload instead of failing the stock
  gates. build-snapshot-from-dockerfile.sh chains it via --aot.

- run_setup.rs: VPOD_SETUP_PATIENCE env multiplies the per-step
  instruction budget (default 1, unchanged). Heavy runtimes exceed the
  default window under aot-trace instrumentation; the custom AOT pass
  sets 25 instead of every caller paying for a bigger default.

- guest/entropy/vpod_seed_entropy.c: deterministic emulation produces
  no jitter entropy, so the guest crng never initializes and blocking
  getrandom() hangs forever — node/V8 blocks at startup (python never
  does, which is why the stock images never hit it; observed as ~20
  CPU-hours inside one node -e). The finalize boot now seeds AND
  credits the crng (RNDADDENTROPY) from host randomness baked into the
  overlay, deleted before capture, hard-gated by a marker. Inherent
  snapshot caveat documented in the helper: all resumes share captured
  RNG state; fresh per-resume entropy would need emulator support.

Measured on the demo image (node+git+python trace): 1432 translated
pages; 506M of 4.4B guest insns (11.4%) retire through AOT blocks at
450 insns/dispatch on a mixed workload, 0 without.

Signed-off-by: Lars Trieloff <lars@trieloff.net>
Copilot AI lite review requested due to automatic review settings August 20, 2026 18:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@mavdol

mavdol commented Aug 21, 2026

Copy link
Copy Markdown
Member

Thanks for this PR and the Linux compatibility!

I tested the new build-custom-snapshot.sh alongside build-default-snapshot.sh, and it works great. Here's the Dockerfile for build-custom-snapshot.sh :

FROM alpine:3.23.0

RUN apk add --no-cache --no-scripts ca-certificates python3 uv bash

Build cost (from a local macos)

builder user time
build-custom-snapshot.sh (warm layer cache) 56.20s user, 3.99s sys
build-default-snapshot.sh 67.95s user, 1.83s sys

Performances at runtime

workload build-default-snapshot.sh Dockerfile (via build-custom-snapshot.sh) delta
engine + component load 0.210s 0.207s -1.4%
Sandbox.create() 0.021s 0.010s
first command (guest restore) 0.373s 0.297s -20%
code.run('pass') warm 0.013s 0.014s
commands.run('echo hi') 0.013s 0.009s
python3 -c pass 0.082s 0.075s -8.5%
uv venv 2.512s 2.542s +1.2%
uv pip install six 2.596s 2.638s +1.6%
rg over 120 files / 48K lines 0.178s 0.180s +1.1%
wget http 0.075s 0.089s +19%
wget https (TLS proxy) 0.167s 0.161s -3.6%

I'll take care of the CI (but seems to be github related) and merge it

@mavdol
mavdol merged commit b0ba41e into capsulerun:main Aug 21, 2026
20 of 22 checks passed
@trieloff

Copy link
Copy Markdown
Contributor Author

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants