Skip to main content

TunaOS Architecture

A full overview of how TunaOS is made. It starts at the upstream base image and ends at a booted desktop on real hardware.

This page is the map. Each section links to the reference doc that holds the detail, and to the code that is the source of truth.


1. What TunaOS is​

TunaOS is a family of bootc images. Each image is an OCI container that a machine can boot. The system is immutable: /usr comes from the image, and an update replaces the whole image, not single files.

One recipe builds every variant. A variant is one upstream base, such as Fedora or Debian. A flavor is one desktop on that base, such as gnome or kde. The same scripts run on all of them.

Six package managers are in use: dnf, apt, zypper, pacman, emerge and the tunaos package factory. This is the main source of complexity in the repository, and most of the code that looks odd exists to hold those six paths together.

                        upstream base image
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ variant (9 of them) β”‚ flounder = Debian 13
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ flavor / desktop β”‚ gnome kde xfce niri cosmic
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ hardware layer β”‚ hwe, nvidia, asahi
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
published image
ghcr.io/tuna-os/…

The variants​

VariantBasePackage manager
yellowfinAlmaLinux Kitten 10dnf (el10)
albacoreAlmaLinux 10dnf (el10)
skipjackCentOS Stream 10dnf (el10)
redfinRHEL 10dnf (el10)
bonitoFedora 44dnf (fedora)
bonito-rawhideFedora Rawhidednf (fedora)
grouperUbuntu 26.04apt
flounderDebian 13 Trixieapt
flounder-sidDebian Sidapt
marlinArch Linuxpacman
sailfinopenSUSE Tumbleweedzypper
guppyGentoo Linuxemerge

The authority for this table is .github/build-config.yml. If the two disagree, the file is right.


2. Repository map​

TunaOS is more than one repository. Here is what each one owns.

tuna-os/tunaOS ............ the images. Build scripts, desktop manifests,
CI matrix, install and boot tests.
tuna-os/tacklebox ......... the ISO engine, in pure Go. Registry pull,
layer unpack, EROFS, FAT ESP, ISO9660.
tuna-os/iso-builder ....... the web app. Runs tacklebox as WebAssembly
so a browser can make an ISO.
tuna-os/tunaos-packages ... the package factory. Builds RPMs and DEBs
for software the upstream repos do not ship.
tuna-os/remora ............ rebuilds user layers on the installed system.
tuna-os/corral ............ local VM and lab tooling.
tuna-os/docs .............. this documentation site.

3. How an image is made​

CI reads the matrix from .github/build-config.yml. It then runs a Containerfile for the package family of that variant. There is one per family: Containerfile.el10, Containerfile.debian, Containerfile.ubuntu, Containerfile.arch, Containerfile.opensuse and Containerfile.gentoo.

Each Containerfile calls the numbered scripts in build_scripts/. The numbers set the order.

build_scripts/
00-copy-files.sh ........ system_files/ into the image
01-workarounds.sh ....... per-base fixes that upstream has not landed
10-base-packages.sh ..... the packages every variant gets
20-packages.sh .......... the packages this variant gets
26-packages-post.sh ..... packages that need the ones above
40-services.sh .......... systemd units on or off
90-image-info.sh ........ /usr/share/ublue-os/image-info.json, os-release
91-arch-customizations.sh
99-cleanup.sh ........... caches out, image smaller

The desktop arrives in a later stage, not in the list above. That stage calls build_scripts/desktop/install-desktop.sh <desktop>.

Desktop manifests​

install-desktop.sh reads one YAML file per desktop from manifests/desktops/. The file holds one package list per package manager.

Two overrides exist. <desktop>-arch.yaml wins on Arch, and <desktop>-debian.yaml wins on Debian. Ubuntu uses the plain file.

manifests/desktops/gnome.yaml
packages:
fedora: [ … ] 52 packages
el10: [ … ] 27 packages
apt: [ … ] 12 packages
zypper: [ … ] 62 packages
emerge: [ … ] 2 packages

The list lengths differ a lot, and that is correct. Gentoo needs two entries, because a meta package pulls in the whole desktop. openSUSE needs sixty-two because its patterns hold the session alone.

A package count is therefore not a measure of desktop completeness. Section 7 covers the check that is.


4. How an ISO is made​

Two paths make an ISO. Both call the same Go packages in tacklebox, so both produce the same media.

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ published bootc image β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ native path β”‚ β”‚ browser path β”‚
β”‚ cmd/purebuild β”‚ β”‚ tbox.wasm β”‚
β”‚ runs in CI β”‚ β”‚ runs in a tab β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ bootable ISO β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What the engine does​

The steps are the same on both paths.

  1. Pull the manifest and layers from the registry.
  2. Unpack the layers into a tree, with OCI overlay rules.
  3. Graft the live overlay, and add the live user and autologin.
  4. Write an EROFS filesystem that holds the root.
  5. Write a FAT partition for EFI, with the kernel and the boot loader.
  6. Write an ISO9660 image with an El Torito boot record.

No step needs root. Ownership and modes live in the tree, never on a real filesystem. internal/purefs holds the writers, and internal/oci holds the registry and unpack code.

Why the browser path is hard​

tbox.wasm is Go compiled to wasm32. The engine uses one linear memory. It is 32 bits wide, so about 4 GiB is a hard ceiling. Go cannot target Memory64, so no host setting raises it.

The engine therefore holds no file content in memory. Layer bodies stream to OPFS, and the engine reads them back in 4 MiB slices. OPFS is origin private storage, and the browser gives it to the page. The heap holds the tree and the inode table only.

See tacklebox docs/opfs-streaming-handoff.md for the measurements behind that design.


5. The live boot chain​

An ISO must hand control through several stages before a desktop appears. A fault at any stage looks the same to a user: a black screen.

  firmware (OVMF / UEFI)
β”‚
β–Ό
El Torito boot record ──▢ EFI system partition
β”‚
β–Ό
systemd-boot ──▢ loader entry ──▢ kernel + initramfs
β”‚
β–Ό
tbox dracut modules 90tbox-live, 95tbox-root
β”‚ parse cmdline, find the media
β–Ό
mount the ISO ──▢ loop mount the EROFS root
β”‚
β–Ό
overlay sysroot lowerdir = /run/rootfsbase
β”‚
β–Ό
systemd ──▢ display manager ──▢ desktop session

The kernel needs modules to mount the root: erofs, isofs, loop, overlay, squashfs, sr_mod and cdrom. The engine lifts them out of the image and puts them in the initramfs overlay, because a stock initramfs may not hold them.

scripts/test-live-boot.sh in tacklebox drives this whole chain under QEMU. It looks for a marker on the serial console and for a login prompt. That script has a header. It lists four faults that pass every other check and fail here.


6. Install and day two​

The live ISO runs an installer. After the install, the machine boots the same image from disk, and bootc handles updates.

remora carries user choices across that boundary. The ISO holds a manifest at /LiveOS/remora/remora.yaml with extra packages and repositories. On the installed system, remora rebuilds those layers on the upstream base and calls bootc switch. The customisation then survives updates.


7. The gates​

TunaOS has many checks. They are worth a section, because most faults found in 2026 were faults where a check passed and the artifact was still broken.

GateWhereWhat it proves
Lint.github/workflows/lint.ymlshell, YAML, JSON, actions syntax
Desktop contract, buildbuild_scripts/checks/verify-desktop-experience.shthe desktop is usable, not only startable
Desktop contract, runtimetunaos-desktop-contract.servicemarkers on ttyS0 after an install
ISO formatiso-builder @full Playwright testsize and the CD001 magic
Live boottacklebox scripts/test-live-boot.shthe chain in section 5 reaches a login
Install and LUKSscripts/iso-e2e.sh, scripts/e2e-luks-checks.shan encrypted install boots

Two rules that came from real faults​

A session that starts is not a desktop. The contract once asked for a shell, a session file and a display manager unit. One image met all three and still had no file manager, no portal, no secret store and no gvfs. The contract now asks for those too.

A package count is not a verdict. Gentoo ships two entries and gets the largest desktop of any variant. openSUSE shipped three, and got a skeleton. Only component presence separates the two.


8. Where packages come from​

Most packages come from the upstream repositories of the base. When upstream does not ship something, tunaos-packages builds it.

That repository holds a package factory, Tideforge. A recipe gives native metadata for several targets: EL10 and Fedora as RPM, Debian and Ubuntu as DEB, and Arch. Output goes to a repository on Cloudflare R2, and the images add that repository.

This is the only answer when a base has no package at all. Two examples from 2026: niri has no apt package in Debian or Ubuntu, and labwc has none for EL10.


9. Reference​

In the tunaOS repository​

Other repositories​

Upstream​

  • bootc β€” boot a container image
  • Universal Blue β€” the wider ecosystem
  • EROFS β€” the read-only root filesystem
  • dracut β€” the initramfs