Skip to content

Add Alpine rootfs flavor alongside Ubuntu #264

@aniketmaurya

Description

@aniketmaurya

Context

Today the layered image build (#263) ships one rootfs flavor per preset: Ubuntu 24.04 with Node 22, Python 3, uv, git, openssh. Base rootfs ext4 is 419 MB (arm64) after cleanup; each preset layers ~150–900 MB on top.

Alpine 3.20 with musl libc is ~7 MB compressed at the Docker level vs Ubuntu's ~78 MB. A cleaned Alpine + Node + Python base would land around 200–250 MB ext4 — roughly half the Ubuntu base size and a faster boot to boot.

This issue tracks adding Alpine as an opt-in second flavor users can select per-preset.

Goal

Users pick rootfs flavor at start time:

```
smolvm codex start # default — currently Ubuntu
smolvm codex start --flavor alpine # explicit Alpine
smolvm codex start --flavor ubuntu # explicit Ubuntu
```

Both flavors share the same kernel — Linux's userspace ABI is stable across libc choices, and we already verified our SmolVM-built kernel boots both.

Architecture sketch

Manifest gains a 4th dimension. Today's key is `(preset, arch, vmm)` → 6 rows per preset. Add `flavor`:

```python
Flavor = Literal["ubuntu", "alpine"]

class PublishedImage(BaseModel):
preset: Preset
arch: Arch
vmm: Vmm
flavor: Flavor # new
kernel_url: str
rootfs_url: str
...
```

12 rows per preset (2 archs × 3 vmms × 2 flavors). Kernel is shared across flavors so artifact count on the release page stays modest.

CI workflow in build-published-images.yml gets a flavor matrix dimension:

```yaml
matrix:
arch: [amd64, arm64]
preset: [codex, claude-code, hermes, pi]
flavor: [ubuntu, alpine]
```

Doubles the cells but each Alpine cell is faster (smaller layer copy, faster apk install).

New files under `scripts/ci/`:

  • `Dockerfile.base-alpine-rootfs` — `FROM alpine:3.20`, `apk add` equivalents of the Ubuntu base.
  • Alpine branch in `build-base-rootfs.sh` selected by `FLAVOR` env var.
  • `build-preset.sh` gets per-flavor branches because `apk add` ≠ `apt-get install` and Alpine uses busybox utilities.

CLI flag. Add `--flavor {ubuntu,alpine}` to `smolvm create` and every `smolvm start` command. Default stays Ubuntu until Alpine is fully verified across all presets.

Per-preset risk assessment

Alpine's musl libc breaks some upstream binaries that ship glibc-only prebuilts. Roll out preset-by-preset:

Preset Risk Why
codex low pure JS, npm install only
claude-code low pure JS, npm install only
pi low pure JS
openclaw high `@node-llama-cpp` has native bindings shipped as glibc-only prebuilts. Even after we strip CUDA/Vulkan backends, the remaining `linux-x64-cpu` / `linux-arm64-cpu` may not load on musl.
hermes high `[all]` extras pull ML packages. Many ship `manylinux2014` wheels (glibc) but not `musllinux` variants. uv would fall through to source builds — slow, sometimes broken.

Proposed milestones

  1. Ship Alpine for codex + claude-code + pi (low risk, ~1-2 days). Three pure-JS presets that almost certainly work.
  2. Spike hermes on Alpine with `[all]` swapped for a curated minimal extras set if needed. Document any wheels that fail.
  3. Spike openclaw on Alpine — separate issue if @node-llama-cpp turns out to need a fork or upstream fix.

Each milestone is mergeable independently. Don't gate the whole rollout on the riskiest preset.

Acceptance criteria

  • `smolvm codex start --flavor alpine` boots, SSH works, `codex --version` runs.
  • Same for `claude-code` and `pi`.
  • Default stays Ubuntu unless explicitly selected.
  • Alpine rootfs published under the same draft release tag as Ubuntu (one tag, multiple artifacts: `--ubuntu-rootfs.ext4.zst`, `--alpine-rootfs.ext4.zst`).

What's NOT in scope

  • Alpine for openclaw or hermes — separate issues if/when we tackle them.
  • Distroless / Wolfi / other non-Alpine non-Ubuntu flavors — premature.
  • Switching default to Alpine — measure stability under real usage first.
  • A flavor-aware `smolvm prune` that distinguishes orphan Alpine vs Ubuntu caches — straightforward extension once flavors land.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions