genpack — A Gentoo-Based Immutable System Image Build Toolchain

Introduction

genpack is a suite of tools for declaratively building, distributing, and booting purpose-built immutable system images based on Gentoo Linux. Rather than following the traditional approach of "install an OS and then customize it," genpack adopts an image factory philosophy: build the entire OS image from a blueprint (JSON5).

What Docker achieved for application containers, genpack aims to achieve at the entire OS level, spanning bare-metal machines, virtual machines, and embedded devices.

Toolchain Architecture

genpack-overlayGentoo overlay:
profiles & ebuilds
genpackImage builder
genpack-installDisk writer / ISO /
self-update
.squashfs / .img ↓
genpack-artifactsImage definition collection
genpack-initBoot-time
provisioning
vmRun as a VM
on QEMU/KVM

Component Overview

ComponentLanguageRoleRepository
genpackPython + C++Generate SquashFS images from JSON5 definitionswbrxcorp/genpack
genpack-overlayGentoo ebuild/profilePackage definitions, profile hierarchy, and initialization scriptswbrxcorp/genpack-overlay
genpack-initC++ + Python (pybind11)Configure the system at every boot based on system.iniwbrxcorp/genpack-init
genpack-installC++Deploy images to disk/ISO/ZIP with self-update capabilitywbrxcorp/genpack-install
vmC++Run and manage genpack images on QEMU/KVMshimarin/vm
genpack-artifactsJSON5 + shell scriptsCollection of concrete image definitionsgenpack-artifacts (GitHub Org)

genpack — The Image Builder

GitHub: wbrxcorp/genpack

genpack is the core build engine of the toolchain. It takes a declarative configuration file called genpack.json5 as input and produces optimized SquashFS images using Gentoo Linux's stage3 and Portage.

How Builds Work: Layered Architecture

genpack uses a two-layer lower/upper architecture for building.

  1. Lower layer: A complete build environment based on Gentoo stage3, where all packages are compiled and installed inside a systemd-nspawn container
  2. Upper layer: Selectively copies only the runtime-necessary files from the lower layer, then performs finishing touches such as user creation, service enablement, and custom file placement
  3. Pack: Compresses the upper layer into a SquashFS image to produce the final artifact

This design ensures that compilers, header files, and other build-time-only files remain in the lower layer and are never included in the final image. A minimal runtime image is obtained naturally.

Declarative Configuration

{
  name: "nextcloud",
  profile: "paravirt",
  packages: [
    "www-apps/nextcloud",
    "dev-db/mysql",
    "dev-lang/php",
    "net-misc/redis"
  ],
  services: ["apache2", "mysqld", "redis"],
  users: [{ name: "nextcloud", uid: 1000 }],
  use: {
    "dev-lang/php": "+mysql +curl +gd +xml +zip"
  },
  compression: "xz"
}

Package lists, enabled services, user definitions, USE flags, and even kernel configuration are all consolidated in this single file. This eliminates manual system construction and achieves fully reproducible builds.

Key Commands

CommandFunction
genpack buildFull build (lower → upper → pack)
genpack lowerBuild/rebuild the lower layer
genpack upperBuild/rebuild the upper layer
genpack packGenerate the SquashFS image
genpack bashInteractive shell inside the lower layer
genpack archiveCreate a tar.gz archive of the configuration

Supported Architectures

x86_64, aarch64 (ARM64), i686, and riscv64 are supported. Architecture-specific settings can be managed through the arch section of genpack.json5.


genpack-overlay — Gentoo Overlay

GitHub: wbrxcorp/genpack-overlay

genpack-overlay is a Gentoo Portage overlay that provides the building blocks for genpack images: metapackages that bundle related functionality and a profile hierarchy that abstracts away deployment target differences.

Profile Hierarchy

genpack/base (common to all targets: kernel, init, base tools)
  ├── genpack/systemimg (bare-metal: hardware detection, storage tools)
  │     └── systemimg/baremetal (BIOS/UEFI, device drivers)
  ├── genpack/paravirt (virtualized: QEMU guest agent, virtio)
  ├── genpack/gnome (GNOME desktop)
  └── genpack/weston (Wayland compositor)

Image definitions simply specify profile: "paravirt" or profile: "gnome/baremetal", and the appropriate base environment is selected automatically.

Package Scripts

Each package can place initialization scripts under /usr/lib/genpack/package-scripts/. Package-specific setup tasks are defined here, such as MySQL data directory initialization, Docker storage configuration, and SSH host key generation.


genpack-init — Boot-Time Provisioning System

GitHub: wbrxcorp/genpack-init

genpack-init is a hybrid initialization system written in C++ and Python (pybind11). It starts as PID 1 and, at every boot, reads system.ini from the boot partition and executes Python scripts to configure the system.

Boot Sequence

  1. Starts as PID 1
  2. Reads /run/initramfs/boot/system.ini (located on the FAT partition)
  3. Executes the configure(ini) function in each module under /usr/lib/genpack-init/*.py
  4. Applies hostname, network, storage, and service configuration
  5. Hands off execution to the real init (/sbin/init = systemd) via exec

Native Capabilities Exposed to Python

Through pybind11, genpack-init exposes the following low-level operations to Python scripts:

system.ini-Driven Architecture

The most important design characteristic of genpack-init is that a single system.ini on the FAT partition is all it takes to customize the entire system's behavior.

Partition 1: FAT32 (Boot)
EFI/ — Bootloader
system.img — SquashFS OS image
system.ini ← The only file the user edits
Partition 2: Data [optional]

Root Filesystem Layout: Flexible Persistence via overlayfs

The genpack image's initramfs detects the presence of a data partition on the boot storage (or a data virtual disk in the case of QEMU) at startup and dynamically determines the upper layer of the overlayfs root filesystem:

This mechanism allows the same image to exhibit different operational characteristics depending on the deployment configuration. A setup without a data partition enables "reset every reboot" operation suited for kiosk terminals and demo environments, while a setup with a data partition enables persistent operation suited for servers. In either case, genpack-init reads system.ini and configures the system at every boot, so changes to system.ini are always reflected on the next startup.

Benefits of the system.ini Approach


genpack-install — Image Deployment Tool

GitHub: wbrxcorp/genpack-install

genpack-install writes the generated system image to physical storage and makes it bootable.

Operating Modes

ModePurpose
--disk=<device>Install to a physical disk (partitioning + bootloader setup)
Self-updateAtomically replace the image on a running system
--cdrom=<file>Create a bootable ISO 9660 image
--zip=<file>Create a ZIP archive

Multi-Architecture Boot Support

genpack-install generates and installs GRUB bootloaders for BIOS / UEFI (x86_64, i386, ARM64, RISC-V) / Raspberry Pi boot methods. El Torito CD/ISO booting is also supported.

Atomic Self-Update

Image updates on a running system are performed through the following atomic rename sequence:

system     → system.cur  (back up the current image)
system.new → system      (activate the new image)
system.cur → system.old  (retain the previous generation = rollback possible)

vm — Virtual Machine Management Tool

GitHub: shimarin/vm

vm is a command-line tool for running and managing genpack system images as virtual machines on QEMU/KVM.

Key Commands

CommandFunction
vm run <image>Boot a VM from a system image
vm serviceRun VMs as services based on vm.ini
vm console <name>Connect to a VM's serial console
vm stop <name>Graceful shutdown via QMP protocol
vm listList running VMs
vm ssh user@vmSSH into a VM via vsock
vm usbEnumerate USB devices (with XPath query support)

Key Features


genpack-artifacts — Image Definition Collection

GitHub Organization: genpack-artifacts

genpack-artifacts is a collection of concrete system image definitions built with the genpack toolchain. Each artifact is managed as an individual repository.

Artifact Structure

artifact-name/
├── genpack.json5          # Declarative image definition
├── files/                 # Files merged into the root filesystem
│   ├── build.d/           # Scripts executed at build time
│   ├── etc/               # Configuration files
│   └── boot/              # Boot configuration (grub.cfg, etc.)
├── kernel/                # Kernel customization (optional)
│   └── config.d/          # Kernel configuration fragments
└── savedconfig/           # Portage savedconfig (per architecture)

Example Artifacts

CategoryArtifactPurpose
Desktopgnome, streamerGNOME desktop, OBS streaming workstation
ML/AItorchPyTorch + ROCm/CUDA machine learning environment
Cloudnextcloud, owncloudSelf-hosted cloud storage
Project managementredmineRedmine project management
Networkingvpnhub, walbrixVPN gateway, network appliance
Securitysuricata, borgIDS, backup server
EmbeddedcameraMotion-detection camera system
Utilitiesrescue, stubSystem recovery, multi-distribution VM provisioning

From minimal configurations (rescue: a dozen or so packages) to full desktops (gnome: hundreds of packages), everything is defined using the same genpack.json5 + files/ pattern.


Design Philosophy

1. Declarative Image Definition (Infrastructure as Code)

Every image is declaratively defined in genpack.json5. Packages, USE flags, users, services, and kernel configuration are all consolidated in a single file, enabling reproducible builds with no manual intervention.

2. Immutable Image, Flexible Persistence

The final artifact is a read-only SquashFS image. System updates are performed not by modifying the running environment but by building a new image and atomically swapping it in. The runtime root filesystem is composed using overlayfs, where the upper layer is automatically backed by either persistent storage or tmpfs depending on whether a data partition exists. Without a data partition, the system boots clean every time, making configuration drift structurally impossible. With a data partition, persistent operation suitable for server workloads is available.

3. Complete Separation of OS and User Configuration

The OS image (SquashFS) is immutable; user configuration (system.ini) is a plain text file on the FAT partition. Because these two are completely separated, OS updates never destroy configuration, and modifying settings requires no Linux expertise.

4. Configuration Applied at Every Boot

genpack-init reads system.ini and configures the system not just on the first boot, but on every boot. This ensures that changes to system.ini are always reflected on the next startup. Furthermore, in transient mode (no data partition), the overlayfs upper layer is backed by tmpfs, so runtime changes vanish on reboot and the system always starts from a clean state plus the system.ini configuration.

5. Minimal Images Through Layered Builds

By separating the lower layer (build environment) from the upper layer (runtime), compilers and headers that are unnecessary at runtime are naturally excluded.

6. Why Gentoo

Choosing Gentoo as the base distribution provides:

7. Multi-Architecture, Multi-Target

The toolchain provides cross-cutting support for x86_64 / aarch64 / i686 / riscv64 architectures and BIOS / UEFI / Raspberry Pi boot methods.


End-to-End Workflow

  1. Design: Write a genpack.json5 definition
  2. Build: genpack build
  3. Deploy (choose one)
  4. Boot: genpack-init reads system.ini and configures the system
  5. Operate: Edit system.ini and reboot = configuration change / Deploy a new image via self-update = OS update

What Makes genpack Unique

Many tools exist with similar goals, and most of them are more mature than genpack.

ToolApproachBase
NixOSDeclarative OS configuration with hash-based reproducibilityNix package manager
Yocto / OpenEmbeddedEmbedded Linux image generation from sourceBitBake build system
BuildrootSimple embedded Linux builderCustom Kconfig-based
mkosiOfficial systemd image builderBinary packages from Debian/Fedora/Arch, etc.
OSTree (Silverblue, etc.)Immutable OS + atomic delta updatesFedora/RHEL
Ignition (CoreOS/Flatcar)First-boot provisioningContainer Linux
cloud-initCloud-oriented first-boot configurationVarious distributions

genpack has neither the theoretical rigor of NixOS, nor the enterprise readiness of Yocto, nor the delta update efficiency of OSTree. Yet it occupies a distinctive position for the following reasons.

A Deliberately Primitive Interface: FAT32 + INI

ToolConfiguration InterfaceRequired Environment for Editing
NixOSconfiguration.nix (Nix language)Understanding of Nix + text editor
IgnitionJSON (machine-readable)Typically generated by another tool
cloud-initYAMLText editor + YAML knowledge
Kairos / TalosYAML APIDedicated CLI / API
genpackINI on FAT32Windows Notepad

Many immutable OSes use YAML or JSON to convey configuration, but these are interfaces designed by engineers for engineers. cloud-init is powerful, but a YAML indentation mistake mercilessly rejects beginners. Ignition is not even intended to be written by hand.

genpack intentionally keeps its configuration interface far from technical sophistication. FAT32 is the one filesystem that can be mounted by any OS, and INI is the simplest structured format that can be edited in Notepad without breaking anything. This combination is a deliberate design choice to hand operational control of a highly optimized Linux system to non-technical users.

Every-Boot Execution + Automatic Persistence Mode via Hardware Detection

Ignition is designed to run only on the first boot, reflecting the cloud-native assumption that "provisioning happens once and that's enough." cloud-init follows essentially the same model.

genpack-init runs on every boot. Moreover, it automatically determines whether the overlayfs upper layer should use persistent storage or tmpfs based on the physical presence or absence of a data partition (or virtual disk).

Switching between these modes requires no configuration change whatsoever — it is determined entirely by the hardware setup. Simply "removing the data disk" as a physical operation is enough to turn a server into a kiosk. This is an idea not found in Ignition or cloud-init.

Using Gentoo as the Foundation for Immutable Images: An Inversion

Gentoo is known as the distribution where "you customize everything yourself, continuously" — the quintessential mutable system. genpack uses this very Gentoo as raw material for immutable images.

Yocto also "builds from source to create immutable images," but BitBake's proprietary build system and recipe format impose a steep learning curve. genpack leverages Gentoo's existing ecosystem (Portage, ebuilds, profiles, USE flags) as-is without inventing any new package format or build system. This allows the advantages of Gentoo — source-level optimization control, precise dependency reduction through USE flags, and a vast package repository — to be enjoyed directly in the context of immutable images.

Implicit Minimization Through Lower/Upper Separation at Build Time

Docker's multi-stage build requires an explicit operation to "separate the build stage from the runtime stage." Nix automatically tracks runtime dependencies through closure analysis, but this relies on Nix's sophisticated machinery.

genpack's lower/upper separation is a more straightforward yet effective technique. The lower layer installs all packages (including build dependencies) and records the file lists. The upper layer then selectively copies only files belonging to runtime packages. Files from packages designated as buildtime_packages are not copied to the upper layer, preventing GCC and header files from contaminating the final image. This is a minimization mechanism unique to genpack, built on Portage's per-package file tracking capability.

genpack-init's pybind11 Plugin Architecture

cloud-init is written in Python, but extending its modules requires understanding cloud-init itself. Ignition is a monolithic Go binary that fundamentally does not anticipate extension. Many embedded init systems are shell-script-based.

genpack-init implements low-level operations (disk manipulation, coldplug, mount) in C++ and exposes them to Python via pybind11. The provisioning logic itself is written as Python scripts with a configure(ini) function, placed in /usr/lib/genpack-init/ as plugins. Adding new configuration capabilities is as simple as writing a single Python file and placing it in the designated directory — no C++ recompilation needed. This achieves both the performance of native hardware operations and the ease of writing configuration logic.

Summary

What makes genpack distinctive is not any single technical advantage, but rather the way ideas like "INI files on FAT32," "automatic operating mode selection based on hardware configuration," "repurposing the Gentoo ecosystem for immutable images," and "plugin-based init via pybind11" are combined under a coherent design philosophy aimed at "distributing highly optimized Linux images as appliances that non-technical users can operate." The essential uniqueness lies not in technical edge, but in a consistent concern for "who will actually be touching this system."