Generate flashable, headless Raspberry Pi images (no setup-alpine, no rpi-imager-pre-fill required)
Project description
pi-bake
Generate flashable, headless Raspberry Pi images. Flash one
.img.gz per Pi, boot, SSH in. No setup-alpine interactive
walk, no rpi-imager GUI clicking through pre-fill, no console
on the Pi.
What gets baked
Per node, from CLI flags or the Python API:
- Hostname →
/etc/hostname - SSH pubkey →
/root/.ssh/authorized_keys(mode 0600) + sshdPasswordAuthentication no - WiFi creds (optional) →
/etc/wpa_supplicant/wpa_supplicant.confso the Pi auto-joins on first boot. Omit for wired-only. - Timezone, regulatory country (sensible defaults)
- First-boot script that
apk adds the small set of packages (openssh-server, iproute2, etc.), enables services, then self-disables.
That's it. No role-specific code, no totaldns, no platform lock-in. Once the Pi is on the network, whatever orchestrator you use (pyinfra, Ansible, plain SSH) takes over.
Install
pip install pi-bake
System tools (one-time per dev machine):
# Fedora
sudo dnf install mtools dosfstools xz util-linux openssl tar cpio
# Debian / Ubuntu
sudo apt install mtools dosfstools xz-utils util-linux openssl tar cpio
# Alpine
apk add mtools dosfstools xz util-linux openssl tar cpio
Tooling by backend:
| Backend | Tools | Root? |
|---|---|---|
| Alpine | mtools + dosfstools + tar + cpio + openssl + ssh-keygen | NO |
| Raspbian / Debian / Fedora | xz + losetup + mount + partprobe + lsblk + ssh-keygen | YES (losetup needs CAP_SYS_ADMIN) |
For the sudo-requiring backends, the typical operator setup is either:
- Run pi-bake inside a privileged LXC container that gives it
root without prompting your host. LXC setup is outside this
project's scope (use whatever container/VM workflow you prefer);
pi-bake just needs to be able to
losetup+mount. - Add passwordless sudoers entries for the specific commands
pi-bake invokes (
losetup,mount,umount,partprobe,tee,chmod,chown,mkdir,sh -c "cat >>"). Restrictive enough to be safe; broad enough to bake without prompting.
The Alpine baker doesn't need any of this — runs as a regular user via mtools.
Quick start
# What can we bake for what?
pi-bake list-boards
pi-bake list-os --board pi-zero-2-w
# Bake an Alpine image for a Pi Zero 2 W with WiFi creds.
pi-bake build \
--board pi-zero-2-w \
--os alpine \
--hostname pi-radio-1 \
--ssh-pubkey ~/.ssh/id_ed25519.pub \
--wifi-ssid totaldns-lab \
--wifi-psk secret \
--out ~/sdcards/pi-radio-1.img.gz
# Flash. Replace mmcblk0 with your SD card's actual device.
zcat ~/sdcards/pi-radio-1.img.gz | sudo dd of=/dev/mmcblk0 bs=4M status=progress
# Boot the Pi. Wait ~30s. Then:
ssh root@pi-radio-1.lan uptime
For wired-only nodes (eth0), omit the WiFi flags:
pi-bake build \
--board pi-5 --os alpine --hostname boat \
--ssh-pubkey ~/.ssh/id_ed25519.pub \
--out ~/sdcards/boat.img.gz
YAML recipes (v0.0.5+)
Operators bake the same image from a YAML recipe. Better for multi-node deployments + version control:
pi-bake build --config ~/recipes/pi-5.yaml
Already have a working CLI invocation? Round-trip it to YAML:
pi-bake build \
--board pi-5 --os alpine --hostname boat \
--ssh-pubkey ~/.ssh/id_ed25519.pub \
--out ~/sdcards/boat.img.gz \
--to-yaml ~/recipes/boat.yaml \
--no-bake
--no-bake skips the actual image build — useful when you only
want to capture the recipe. Drop it to bake AND save the YAML.
References:
pi-bake.example.yaml— annotated reference for every field, dnsmasq-style. Read once, never go hunting for options again.examples/— minimal, tested recipes for common shapes (wifi-station, wired AP, BE200 host on Alpine edge).
Validate a recipe without baking:
pi-bake build --config recipe.yaml --to-yaml /tmp/normalized.yaml --no-bake
This runs the strict validator (unknown keys are an error) and writes a canonical normalized YAML — surfaces schema errors instantly while you're editing.
Supported boards × OSes
| Board | Alpine | Raspbian | Debian | Fedora |
|---|---|---|---|---|
| Pi Zero W | ✓ (armhf) | ✗ (32-bit ARMv6 not packaged) | ✗ | ✗ |
| Pi Zero 2 W | ✓ | ✓ (32-bit ARMv7 / arm64) | ✗ | ✗ |
| Pi 3 | ✓ | ✓ | ✓ | ✗ |
| Pi 4 | ✓ | ✓ | ✓ | 🟡 |
| Pi 5 | ✓ (3.21+) | ✓ | ✗ (raspi.debian.net has no Pi 5 build) | 🟡 |
🟡 = Fedora bake produces a configured Fedora rootfs but the
upstream Fedora ARM image isn't Pi-specific — operator must run
arm-image-installer --target=rpi4|rpi5 on the output to inject
Pi firmware before flashing. Pi-bootloader-shim is a future
pi-bake feature (see ROADMAP.md).
Run pi-bake list-os --board <b> for the current matrix.
Status
All four backends (Alpine, Raspbian, Debian, Fedora) are working as of v0.3. Alpine is the no-root, mtools-based path (easiest to run from any Linux host). Raspbian / Debian / Fedora use losetup + mount and require sudo (typically via LXC — see above).
Python API
The CLI is a thin wrapper around pi_bake.build():
from pi_bake import build, NodeConfig
build(
board="pi-zero-2-w",
os_name="alpine",
version=None, # latest known-good
node=NodeConfig(
hostname="pi-radio-1",
ssh_pubkey=open(".../id_ed25519.pub").read(),
wifi_ssid="totaldns-lab",
wifi_psk="secret",
),
out_path="~/sdcards/pi-radio-1.img.gz",
)
Roadmap
See ROADMAP.md for the numbered backlog + state table. Highlights:
- Dynamic OS version discovery. Pull
https://dl-cdn.alpinelinux.org/alpine/and the rpi downloads index instead of the hardcodedversionstuples in the catalog. - v0.3 — multi-image batch.
pi-bake build-many topology.jsonemits an.img.gzper node entry in one go. - v0.3 — static IP option. Today we DHCP from first reachable
iface; static-IP-only deployments need a
--static-v4flag. - v0.3 — encrypted overlay for the rootfs (LUKS).
Why does this exist
Three reasons to bake images instead of using
rpi-imager pre-fill or hand-running setup-alpine:
- Per-node config in a script. Topology files (any shape —
JSON, YAML, a hand-written shell script) drive
pi-bake buildin a loop. Lab grows from 1 Pi to 20 without 20 separate keyboard sessions. - Reproducible. Same inputs → same
.img.gz. Re-flash a replacement SD card identically; clone a node by changing the hostname. - Alpine RPi has no
rpi-imagerpre-fill equivalent. This is the only convenient headless flow for the Pi Zero W / 2 W family running Alpine.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_pi_bake-0.2.2.tar.gz.
File metadata
- Download URL: py_pi_bake-0.2.2.tar.gz
- Upload date:
- Size: 140.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a64442014487374541dce54b8976dc07b38892df0114c4133b73fab0edaa98a6
|
|
| MD5 |
c275f908bfa2a52a98e1f2ddf41131a7
|
|
| BLAKE2b-256 |
7daaf9ef6da5e21dd9f7d2a6c84c5dd505ede3ce38ad75962c27c80742d6c6b5
|
Provenance
The following attestation bundles were made for py_pi_bake-0.2.2.tar.gz:
Publisher:
workflow.yml on kurt-cb/pi-bake
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_pi_bake-0.2.2.tar.gz -
Subject digest:
a64442014487374541dce54b8976dc07b38892df0114c4133b73fab0edaa98a6 - Sigstore transparency entry: 1635587159
- Sigstore integration time:
-
Permalink:
kurt-cb/pi-bake@522a919f0f5bef9003859b93b1eac917bf3ba647 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/kurt-cb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@522a919f0f5bef9003859b93b1eac917bf3ba647 -
Trigger Event:
push
-
Statement type:
File details
Details for the file py_pi_bake-0.2.2-py3-none-any.whl.
File metadata
- Download URL: py_pi_bake-0.2.2-py3-none-any.whl
- Upload date:
- Size: 68.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4e6050a1a98e53391cbb9d7168981fc28440aec754f45faaa9700f736a844d3
|
|
| MD5 |
cf2012b7899b190b56c6bbdd79cce919
|
|
| BLAKE2b-256 |
88d74fa58caeaeedd0179676c852e7ebaee8794ca6f2445207b86e83759eac7b
|
Provenance
The following attestation bundles were made for py_pi_bake-0.2.2-py3-none-any.whl:
Publisher:
workflow.yml on kurt-cb/pi-bake
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_pi_bake-0.2.2-py3-none-any.whl -
Subject digest:
c4e6050a1a98e53391cbb9d7168981fc28440aec754f45faaa9700f736a844d3 - Sigstore transparency entry: 1635587402
- Sigstore integration time:
-
Permalink:
kurt-cb/pi-bake@522a919f0f5bef9003859b93b1eac917bf3ba647 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/kurt-cb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@522a919f0f5bef9003859b93b1eac917bf3ba647 -
Trigger Event:
push
-
Statement type: