| 1 |
# Alloy — bootable container image |
| 2 |
# |
| 3 |
# Built with `podman build`. Published to quay.io/alloy/alloy. |
| 4 |
# See docs/IMAGE.md for the composition strategy and rationale. |
| 5 |
# |
| 6 |
# Layer order optimizes rebuild speed: repos first (rarely change), |
| 7 |
# then fonts (large downloads, pinned), then package installs (change |
| 8 |
# with STACK.md), then config tree (changes most often, so lives at |
| 9 |
# the tail). |
| 10 |
# |
| 11 |
# Single-stage. The image carries no cargo-built binaries: everything |
| 12 |
# ships from a repo. Screen recording is the one gap (see the deferral |
| 13 |
# note in docs/STACK.md#screen-recorder). |
| 14 |
|
| 15 |
# ===================================================================== |
| 16 |
# Runtime image — the bootable container itself. |
| 17 |
# ===================================================================== |
| 18 |
FROM quay.io/fedora/fedora-bootc:43 |
| 19 |
|
| 20 |
# ===================================================================== |
| 21 |
# Third-party repos |
| 22 |
# ===================================================================== |
| 23 |
# Tailscale is not in Fedora main; drop their .repo file directly. |
| 24 |
RUN curl -fsSL -o /etc/yum.repos.d/tailscale.repo \ |
| 25 |
https://pkgs.tailscale.com/stable/fedora/tailscale.repo |
| 26 |
|
| 27 |
# Terra — Fedora repo for parts of the Wayland ecosystem (swww, |
| 28 |
# starship, and some others). Does not carry Rust binaries like |
| 29 |
# nushell or yazi; COPRs below handle those. |
| 30 |
# https://terra.fyralabs.com/ |
| 31 |
RUN dnf install -y --nogpgcheck \ |
| 32 |
--repofrompath='terra,https://repos.fyralabs.com/terra$releasever' \ |
| 33 |
terra-release |
| 34 |
|
| 35 |
# dnf5's copr plugin isn't in the base bootc image; pull it so we |
| 36 |
# can `dnf copr enable` for the Rust-Wayland stragglers. |
| 37 |
RUN dnf install -y 'dnf5-command(copr)' |
| 38 |
|
| 39 |
# ublue-os/staging is the community-maintained COPR that packages |
| 40 |
# much of the Wayland / Rust ecosystem for atomic Fedora derivatives. |
| 41 |
# We're not using their base image, but their COPR is a legitimate |
| 42 |
# adoption (same relationship the rest of Alloy has to Fedora repos). |
| 43 |
# |
| 44 |
# Verified 2026-07-19: it does NOT carry wl-screenrec, which is why |
| 45 |
# that one needed a cargo stage and is now deferred. Check before |
| 46 |
# assuming a Rust Wayland tool is here. |
| 47 |
RUN dnf copr enable -y ublue-os/staging |
| 48 |
|
| 49 |
# nushell binary (Terra has crates only) |
| 50 |
RUN dnf copr enable -y atim/nushell |
| 51 |
|
| 52 |
# yazi binary |
| 53 |
RUN dnf copr enable -y varlad/yazi |
| 54 |
|
| 55 |
# satty (screenshot annotator) is expected in ublue-os/staging; |
| 56 |
# if it turns out to need its own COPR, add here. |
| 57 |
|
| 58 |
# ===================================================================== |
| 59 |
# Fonts pulled from upstream releases (not cleanly packaged in Fedora): |
| 60 |
# - IosevkaTerm Nerd Font (mono, TUI-safe: no ligatures, has NF glyphs) |
| 61 |
# - Atkinson Hyperlegible (sans, high-legibility) |
| 62 |
# |
| 63 |
# These sit ahead of the package list on purpose. They are the largest |
| 64 |
# downloads in the build, and the package list is the line that changes |
| 65 |
# most often; behind it, every package edit re-fetched both archives. |
| 66 |
# The unzip/fontconfig they need comes from its own small layer rather |
| 67 |
# than from the main install, which is what lets the split work. |
| 68 |
# ===================================================================== |
| 69 |
# Both sources are pinned. Unpinned (`releases/latest`, branch `main`) |
| 70 |
# these layers change content without the Containerfile changing, so a |
| 71 |
# cache hit stops meaning "same bytes" and two builds of the same commit |
| 72 |
# can ship different fonts. Bump deliberately. |
| 73 |
# |
| 74 |
# Atkinson Hyperlegible publishes no tags, so it pins to a commit. |
| 75 |
ARG NERD_FONTS_VERSION=v3.4.0 |
| 76 |
ARG ATKINSON_COMMIT=1cb311624b2ddf88e9e37873999d165a8cd28b46 |
| 77 |
|
| 78 |
RUN dnf install -y unzip fontconfig && dnf clean all |
| 79 |
RUN mkdir -p /usr/share/fonts/iosevkaterm-nerd \ |
| 80 |
/usr/share/fonts/atkinson-hyperlegible && \ |
| 81 |
curl -fsSL -o /tmp/iosevkaterm-nf.zip \ |
| 82 |
"https://github.com/ryanoasis/nerd-fonts/releases/download/${NERD_FONTS_VERSION}/IosevkaTerm.zip" && \ |
| 83 |
unzip -o /tmp/iosevkaterm-nf.zip -d /usr/share/fonts/iosevkaterm-nerd && \ |
| 84 |
rm /tmp/iosevkaterm-nf.zip && \ |
| 85 |
curl -fsSL -o /tmp/atkinson-hyperlegible.tar.gz \ |
| 86 |
"https://github.com/googlefonts/atkinson-hyperlegible/archive/${ATKINSON_COMMIT}.tar.gz" && \ |
| 87 |
tar -xzf /tmp/atkinson-hyperlegible.tar.gz -C /tmp && \ |
| 88 |
find /tmp/atkinson-hyperlegible-* -name '*.ttf' -exec cp {} /usr/share/fonts/atkinson-hyperlegible/ \; && \ |
| 89 |
rm -rf /tmp/atkinson-hyperlegible.tar.gz /tmp/atkinson-hyperlegible-* && \ |
| 90 |
fc-cache -fv |
| 91 |
|
| 92 |
# ===================================================================== |
| 93 |
# Package additions — full Alloy stack per docs/STACK.md |
| 94 |
# |
| 95 |
# Sources noted per group. Anything absent from both Fedora main and |
| 96 |
# Terra is downloaded directly (Nerd Fonts, in the layer above). The |
| 97 |
# `flatpak` client is installed so users can pull ungoogled-chromium and |
| 98 |
# other on-demand apps from Flathub post-install; no Flatpaks are |
| 99 |
# provisioned at build or first-boot time. |
| 100 |
# ===================================================================== |
| 101 |
RUN dnf install -y \ |
| 102 |
# Compositor and Wayland session (Fedora main) |
| 103 |
sway \ |
| 104 |
# Portals: -wlr is the wlroots backend sway needs for screencast. |
| 105 |
# -gnome was the wrong backend here (it drives screencast through |
| 106 |
# gnome-shell, which Alloy removes below). -gtk stays for the file |
| 107 |
# chooser. |
| 108 |
xdg-desktop-portal xdg-desktop-portal-gtk xdg-desktop-portal-wlr \ |
| 109 |
# Notifications, screenshot annotate, wallpaper (bar = sway's built-in swaybar) |
| 110 |
mako \ |
| 111 |
satty \ |
| 112 |
swww \ |
| 113 |
# Terminal, multiplexer, editor, shell, prompt |
| 114 |
rio \ |
| 115 |
zellij \ |
| 116 |
helix \ |
| 117 |
nushell \ |
| 118 |
starship \ |
| 119 |
zoxide \ |
| 120 |
direnv \ |
| 121 |
# File managers |
| 122 |
yazi \ |
| 123 |
# Content viewers |
| 124 |
mpv \ |
| 125 |
imv \ |
| 126 |
zathura zathura-pdf-mupdf \ |
| 127 |
# System introspection |
| 128 |
bottom \ |
| 129 |
dua-cli \ |
| 130 |
# Wayland session glue |
| 131 |
cliphist \ |
| 132 |
# Lock + idle. swaylock is the adopted lock surface per |
| 133 |
# docs/STACK.md#lock. A session-lock surface is graphical and |
| 134 |
# cannot be a TUI, so Alloy adopts rather than authors it. The |
| 135 |
# themed config in etc/skel had no package behind it until now. |
| 136 |
swaylock \ |
| 137 |
swayidle \ |
| 138 |
swayosd \ |
| 139 |
playerctl \ |
| 140 |
gammastep \ |
| 141 |
# Continuity |
| 142 |
tailscale \ |
| 143 |
syncthing \ |
| 144 |
restic \ |
| 145 |
# Greeter |
| 146 |
greetd \ |
| 147 |
tuigreet \ |
| 148 |
# First-boot account creation. The bootc Anaconda flow offers no |
| 149 |
# user-creation spoke, so without this an install completes with |
| 150 |
# root locked, greeter set to nologin, and no way to log in. |
| 151 |
# Deliberately NOT initial-setup-gui: with no GUI binary present |
| 152 |
# run-initial-setup takes the TUI branch, which is what Alloy |
| 153 |
# wants regardless of default.target being graphical.target. |
| 154 |
# Retired when the Alloy installer replaces Anaconda (GO task). |
| 155 |
initial-setup \ |
| 156 |
# Cursor, GTK theme |
| 157 |
bibata-cursor-theme \ |
| 158 |
adw-gtk3-theme \ |
| 159 |
# Screenshot capture + region-select (sway has no built-in grab) |
| 160 |
grim slurp \ |
| 161 |
# Browser (Gecko default; ungoogled-chromium is opt-in Flatpak per docs/STACK.md) |
| 162 |
firefox \ |
| 163 |
# Flatpak client (user-installed apps on demand; see docs/STACK.md) |
| 164 |
flatpak \ |
| 165 |
&& dnf clean all |
| 166 |
|
| 167 |
# ===================================================================== |
| 168 |
# Package removals — stock desktop pieces Alloy replaces |
| 169 |
# ===================================================================== |
| 170 |
# fedora-bootc:43 is minimal and probably ships none of these, but keep |
| 171 |
# the remove line for safety in case the base grows. || true swallows |
| 172 |
# the "package not installed" error path. |
| 173 |
RUN dnf remove -y \ |
| 174 |
gdm \ |
| 175 |
gnome-shell \ |
| 176 |
gnome-session \ |
| 177 |
|| true |
| 178 |
|
| 179 |
# ===================================================================== |
| 180 |
# System user for greetd. greetd drops privileges to this account |
| 181 |
# before spawning tuigreet; without it greetd exits with |
| 182 |
# "configured default session user 'greeter' not found". Fedora |
| 183 |
# bootc's minimal user database does not include it. |
| 184 |
# ===================================================================== |
| 185 |
RUN useradd -M -r -s /sbin/nologin greeter |
| 186 |
|
| 187 |
# ===================================================================== |
| 188 |
# Config tree — the etc/ and usr/ trees in the repo map 1:1 into |
| 189 |
# the image. Per-user defaults live under etc/skel/.config/ and |
| 190 |
# etc/skel/.mozilla/ (Firefox first-launch profile seed); system- |
| 191 |
# wide config under etc/; Firefox autoconfig + mozilla.cfg under |
| 192 |
# usr/lib64/firefox/. See docs/IMAGE.md for the layout. |
| 193 |
# ===================================================================== |
| 194 |
COPY etc/ /etc/ |
| 195 |
COPY usr/ /usr/ |
| 196 |
|
| 197 |
# ===================================================================== |
| 198 |
# Systemd presets — shipped via etc/systemd/{system,user}-preset/ |
| 199 |
# in the config tree above. Split across system-preset (greetd, |
| 200 |
# tailscaled) and user-preset (swayosd, syncthing, gammastep). |
| 201 |
# See docs/CONTINUITY.md for rationale. |
| 202 |
# |
| 203 |
# The preset files declare the intended enable/disable state; they |
| 204 |
# do not by themselves create the wants/ symlinks. `systemctl |
| 205 |
# preset-all` reads every preset file and applies it — this must |
| 206 |
# run after the config tree is in place, so it lives here. |
| 207 |
# ===================================================================== |
| 208 |
RUN systemctl preset-all |
| 209 |
|
| 210 |
# ===================================================================== |
| 211 |
# Branding |
| 212 |
# ===================================================================== |
| 213 |
# os-release, plymouth splash, wallpapers ship via the config tree |
| 214 |
# above. No additional layer needed here. |
| 215 |
|
| 216 |
# ===================================================================== |
| 217 |
# Disable third-party repos post-install. |
| 218 |
# ===================================================================== |
| 219 |
# terra, the Rust-Wayland COPRs, and Tailscale are needed only at |
| 220 |
# build time to layer packages Fedora doesn't carry. Left enabled they |
| 221 |
# earn nothing at runtime (a bootc image updates by whole-image pull, |
| 222 |
# not per-package dnf) and actively break bootc-image-builder: its |
| 223 |
# installer depsolve reads every enabled repo but runs in its own |
| 224 |
# environment, where these repos' file:// GPG keys and metalinks can't |
| 225 |
# be resolved. Disable them so only the Fedora repos remain live; the |
| 226 |
# already-installed packages are unaffected. |
| 227 |
RUN sed -i 's/^enabled=1/enabled=0/; s/^enabled_metadata=1/enabled_metadata=0/' \ |
| 228 |
/etc/yum.repos.d/terra.repo \ |
| 229 |
/etc/yum.repos.d/tailscale.repo \ |
| 230 |
/etc/yum.repos.d/_copr:*.repo |
| 231 |
|
| 232 |
# ===================================================================== |
| 233 |
# Pin dnf's $releasever to the Fedora base version. |
| 234 |
# ===================================================================== |
| 235 |
# Alloy's os-release carries its own VERSION_ID (0.0), so dnf/librepo |
| 236 |
# would otherwise expand $releasever to "0.0" and request the |
| 237 |
# nonexistent fedora-0.0 repo (404). Every consumer of the Fedora |
| 238 |
# repos — rpm-ostree package layering on the installed system, and |
| 239 |
# bootc-image-builder's installer depsolve — needs this pinned to the |
| 240 |
# actual base version, independent of Alloy's product version. |
| 241 |
RUN echo 43 > /etc/dnf/vars/releasever |
| 242 |
|
| 243 |
# ===================================================================== |
| 244 |
# bootc validation — fails the build if the image isn't a valid |
| 245 |
# bootable container. |
| 246 |
# ===================================================================== |
| 247 |
RUN bootc container lint |
| 248 |
|