Skip to content

Building with GitHub Actions

Eric Ness edited this page Oct 14, 2026 · 11 revisions

GitHub Actions can build your distro on GitHub's machines: it builds the ISO, boots it in QEMU five ways, and checks the result against your spec. You need no Linux, and it's free in a public repository. There are two ways in, and both run the same reusable workflow, .github/workflows/build.yml in the linuxlive repository.

Route For You need
The template, Eric-Ness/linuxlive-distro Making a distro. Recommended. A browser
A fork of Eric-Ness/linuxlive Changing linuxlive itself (the catalog, the compiler, the boot tests) and testing your change git and Python on your computer

Getting Started walks through the template route once with an example. This page is the reference for both.

The template route

A repository made from the template holds your spec, your art, and this workflow:

name: build

on:
  push:
    branches: [main]
    paths-ignore: [README.md]
  workflow_dispatch:

permissions:
  contents: read
  packages: write

concurrency:
  group: build-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    uses: Eric-Ness/linuxlive/.github/workflows/build.yml@v0.4.2
    with:
      spec: linuxlive.yaml
      publish-image: true

Every push to main builds, except one that only changes README.md. Run workflow on the Actions tab builds by hand. A newer push cancels a build still running.

Versions

@v0.4.2 picks the release of linuxlive to build with. It fixes both the workflow and the pipeline it runs, so a build stays reproducible until you change it. To move to a newer release, change the tag in .github/workflows/build.yml and commit. The releases page lists what changed.

Workflow inputs

Set these under with:. All of them are optional.

Input Default What it does
spec linuxlive.yaml Path of the spec in your repository. Image paths in it are relative to the spec.
name the spec's id Base name of the ISO and the artifacts: <name>.iso, iso-<name>, results-<name>. Letters, digits, ., - and _.
keep-iso true Upload the ISO as an artifact. Set it to false to keep only the test results.
iso-retention-days 7 Days GitHub keeps the ISO artifact.
publish-image false Fedora bases: push the system image to your repository's packages on ghcr.io (needs packages: write), tagged :latest once the build passes. The Fedora installer installs from it and installed systems update from it, so without it a Fedora-based ISO cannot install. Ubuntu bases ignore it.
linuxlive-repository Eric-Ness/linuxlive Where the pipeline comes from. Change it only to test a fork of linuxlive.
linuxlive-ref the release in the @ tag Pipeline version: a tag, branch or commit of linuxlive-repository.

Several distros in one repository

Give each spec its own file and build them side by side with a matrix. Each one gets its own artifacts, named after its id:

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        spec: [kiosk.yaml, classroom.yaml]
    uses: Eric-Ness/linuxlive/.github/workflows/build.yml@v0.4.2
    with:
      spec: ${{ matrix.spec }}

Private repositories

They work, with two limits. Builds use your account's included Actions minutes (GitHub Free includes 2,000 a month), and a build takes about 20 to 40 minutes. GitHub Free also includes only 500 MB of artifact storage for private repositories, which is too small for an ISO. Keep your distro's repository public, or set keep-iso: false and use the runs only as tests.

What a build does

The reusable workflow runs one job, build, on an Ubuntu 24.04 runner with KVM:

  1. Check the spec: python -m linuxlive validate. A bad spec stops here with every problem listed.
  2. Compile spec: python -m linuxlive compile writes the build context.
  3. Build image: podman build installs the base system, desktop and kernel.
  4. Assemble ISO: assemble-iso.sh installs the Flatpak apps and writes the hybrid ISO.
  5. Inspect ISO: python -m linuxlive inspect-iso records its boot structures.
  6. Boot tests: boottest.py boots the ISO in QEMU in five modes. The first mode waits for the desktop and takes screenshots. If the first boot never reaches the running system, the other four are skipped.
  7. Verify: python -m linuxlive verify-results checks the results against the spec. The ISO must have both boot catalogs, the USB partition tables and the boot files. Every boot must reach the running system with the right firmware, Secure Boot on only in the Secure Boot mode, and your distro's name. The desktop must start with the right desktop shell, name, id, version and boot splash, your Flatpak apps must be installed, and your wallpaper's colour must be on screen. No system service may fail, the live session must start no update notifiers, and on Ubuntu every checksum in the ISO's md5sum.txt must match.
  8. Summary: the run's summary page says whether it passed and lists every check.
Mode Firmware Started from
uefi-usb UEFI USB stick (this boot also checks the desktop)
uefi-sb-cd UEFI with Secure Boot on DVD
uefi-cd UEFI DVD
bios-cd BIOS DVD
bios-usb BIOS USB stick

The wallpaper

The Verify step checks that your wallpaper really shows on the booted desktop, by finding its main colour in screenshots. For that the wallpaper must be a PNG (8-bit, non-interlaced) with one dominant colour; see Writing a Spec: Images. A JPEG builds and boots, but fails the Verify step.

Results and artifacts

Every build uploads these, whether it passed or not:

Artifact Kept Contents
iso-<name> 7 days by default (iso-retention-days) the ISO, its SHA-256 checksum and its package list (<name>.iso.packages: each package, its version and its source package)
results-<name> 14 days meta.json (commits of your repository and of linuxlive, and the base), iso.json (the inspect report), and for each boot mode modes/<mode>/: result.json, the serial console log and, for uefi-usb, screenshots of the desktop

Artifacts can only be downloaded while signed in to GitHub. Save the ISO somewhere of your own to keep it longer. GitHub releases are no help here: a release file must be under 2 GiB, and ISOs are 2.7 to 4.5 GiB.

The screenshots in results-<name>/modes/uefi-usb/ are the quickest way to see your distro without booting it.

You can re-run the verifier on your own computer, on any OS, against a downloaded results artifact. It needs a checkout of linuxlive with its requirements installed (see Building Locally step 2). Unzip the artifact and point --results at the folder:

python -m linuxlive verify-results --spec ../my-distro/linuxlive.yaml --results results-my-distro

It prints a PASS or FAIL line for each check (about 55 of them) and ends with VERIFY OK when all pass. Add --check iso,boot to skip the desktop checks, or --expect-commit <sha> to confirm the results come from a particular commit.

The fork route

Use this when you want to change linuxlive itself and see your change build and boot. The repository's own CI, .github/workflows/spike.yml, runs the tests and then builds every example through the reusable workflow, with the pipeline at the commit being tested.

Set up your fork

  1. Fork Eric-Ness/linuxlive. Keep the fork public for free runners.
  2. Open the fork's Actions tab and click I understand my workflows, go ahead and enable them. Forks start with workflows turned off.
  3. Clone the fork and install the requirements:
git clone https://github.com/YOUR-NAME/linuxlive.git
cd linuxlive
python -m venv .venv
. .venv/bin/activate                 # Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install -r requirements-dev.txt
python -m pytest

Add an example

An example in CI is four things:

What Where
The spec examples/<name>.yaml
Its art anywhere under examples/, by convention examples/assets/<name>/
The compiler's reference output tests/golden/<name>/, made by the command below
A build entry its name in the matrix in .github/workflows/spike.yml

The reference output. The unit job runs the test suite, and one test compares the compiler's output for every examples/*.yaml with a saved copy in tests/golden/. A new or changed example, or a change to the compiler, fails that test until you save the new output:

LINUXLIVE_UPDATE_GOLDEN=1 python -m pytest tests/test_compiler.py
python -m pytest

In Windows PowerShell:

$env:LINUXLIVE_UPDATE_GOLDEN = "1"; python -m pytest tests/test_compiler.py; Remove-Item Env:LINUXLIVE_UPDATE_GOLDEN
python -m pytest

Read the diff, and commit tests/golden/ with your change. If unit fails, nothing is built.

The build entry. Build jobs come from the example list in the matrix, one name per spec in examples/:

    strategy:
      fail-fast: false
      matrix:
        example: [testos-ubuntu, testos-fedora, lawndarts-ubuntu, lawndarts-fedora, mydistro]

Every spec in examples/ is compiled and tested by unit, but only the names listed here are built and booted.

Get an ISO from a fork

A push keeps only the test results. To get ISOs, choose Actions → spike → Run workflow, tick Upload the ISOs as artifacts (kept for one day) (the keep_iso input), and run it. A push to the same branch while it runs cancels it. spike also uploads unit-summary: test counts and the list of compiled examples.

Keep your fork up to date

On your fork's main page, click Sync fork. If GitHub reports a conflict, it's almost always the matrix in spike.yml: keep the project's version of the file and add your names again. Then regenerate the reference output, since compiler changes alter it, and push.

Troubleshooting

What you see What to do
"Check the spec" fails The message names each problem; see Writing a Spec: Common errors.
unit fails in test_examples_match_golden_output[<name>] (fork) Regenerate the reference output and commit tests/golden/.
unit fails in another test (fork) Run python -m pytest on your computer to see why.
Warning "No /dev/kvm on this runner" QEMU falls back to emulation, which is very slow. Re-run later; GitHub-hosted Linux runners normally have KVM.
"The first boot never reached userspace" The ISO didn't boot. Open results-<name>/modes/uefi-usb/ and read the serial log for the last thing that happened.
"Verify" fails on the wallpaper Use a PNG with a stronger single colour.
"Verify" fails on something else The message says which check failed. If it's not about your art, open an issue and link the run.
The run was cancelled A newer push or run on the same branch replaced it.
No "Run workflow" button The workflow file must be on the default branch, and Actions must be enabled.

Please keep your own distro in your own repository. examples/ in the main repository holds the project's test distros. Fixes, new catalog apps and new features are welcome as pull requests; see CONTRIBUTING.md.