Follows #245, which baked sccache, worker-build and the musl target into the runner's base. This does the same for the per-run install that still costs the most: Tauri's Linux system packages in CI's Desktop app job.
What it does
- services/runner/base/Dockerfile: a new layer after Java, .NET and Ruby (before the Rust build tools) installs
libwebkit2gtk-4.1-dev libxdo-dev libayatana-appindicator3-dev librsvg2-dev, without Recommends. The rest ofscripts/apt-install.sh tauri(build-essential, curl, wget, file, libssl-dev) was in the base already. base.json gains awebkit2gtkversion (scripts/deploy/image.mjs). - scripts/apt-install.sh installs nothing when every package of the set is installed already, and
--checkwritesinstalled=true|falseto the step's output. - ci.yml (Desktop app), desktop-release.yml (Linux), caches.yml (desktop-linux): a
--checkstep first. When the image has the packages, the apt cache restore and the install are both skipped. Otherwise (a self-hosted runner, or an older image) they run as before. - scripts/deploy/deploy.test.mjs: fails when the script's
tauriset has a package the base doesn't install. Without this test, a package added to the script alone would be skipped on g1t's machines. - Docs: the actions guide says the Tauri packages are there, and the CONTRIBUTING cache table notes the
apt-tauri-entry is only used where the image lacks them.
Measured (before)
These are the last 30 CI runs' Desktop app job (g1t run view):
| Step | Median | Range |
|---|---|---|
| What Tauri needs on Linux | 57.7 s | 26 to 136 s |
| Cache apt's downloads | 9.8 s | 5 to 69 s |
| Desktop app job, whole | 143 s | 65 to 367 s |
Expected after: both steps are skipped, and the --check step takes well under a second (0.01 s in the image). That saves about 68 s per run at the median and up to about 3.4 minutes at the worst, so the Desktop job drops to around 75 s. A cold apt cache can no longer cause the 30-minute timeout. The desktop release's Linux build and the Caches workflow's desktop-linux job save the same minute each time they run.
Image size
The new layer is 0.97 GB unpacked (by base.json's size_bytes measure; docker history shows 743 MB), so the base grows from 4.68 GB to about 5.65 GB (+21%). With Recommends the layer would be 1.58 GB: the extra 207 packages are GStreamer plugins, FFmpeg, VA/VDPAU/Vulkan drivers, fonts and pocketsphinx, none of which a build loads, so they're left out. The biggest required pieces are libllvm15 (mesa), libwebkit2gtk-4.1-0 and libicu-dev.
Verified
- In a test image (the current base plus this layer):
apt-install.sh tauri --check→installed=true, andapt-install.sh tauriskips in 0.01 s. On the current base,--check→installed=falseand the install runs as before. - In that image, with nothing else installed, the desktop app's tests pass (
cargo test --manifest-path apps/desktop/src-tauri/Cargo.toml: 55 passed, 3 ignored).tauri build --bundles deb,rpm,appimagealso makes all three bundles, so leaving out Recommends doesn't break the release's AppImage. cargo test -p g1t-actions --test repository_workflowspasses (20). The new deploy test passes.
Not baked in, and why
- The
tauri-windowsset (clang, LLVM, lld, NSIS), cargo-xwin, zig and cargo-zigbuild: only the releases' cross builds and the Caches workflow use them, and Caches runs those jobs only when an entry is missing (none ran in the last 60 runs). LLVM and clang alone would add more than half a GB. - worker-build's own downloads (wasm-bindgen, esbuild, wasm-opt, restored from
worker-build-tools-…in 7 to 12 s per Rust build job): wasm-bindgen's version follows Cargo.lock, so a baked copy would go stale on the next bump. This is a possible follow-up. - cargo-nextest: nothing uses it, and switching the test runner is out of scope.
- worker-build, sccache and the wasm32/musl targets are already in the base (#245). Their steps take 1 to 2 s.
Rollout
The base is built and pushed by hand (or by runner-base.yml once a self-hosted docker runner is registered and RUNNER_BASE_SELF_HOSTED is set):
- On a machine with Docker that's logged in to Cloudflare's registry, on this branch:
node scripts/deploy.mjs build-base. It builds on the previous base's layers (only this layer and the ones after it are new), pushesg1t-runner:base-<date>-<hash>, and rewritesservices/runner/base.json. - Commit
services/runner/base.jsonto this branch. Until then, CI's TypeScript job fails on "services/runner/base.json records the base its folder builds". That guard is intended: a deploy would refuse to build the runner's image on a stale base.json too. - Merge. The deploy's runner-image stage (
node scripts/deploy.mjs image) builds the runner's image on the new base, and every hosted job after that starts from it. - Check on the next CI run that "Is what Tauri needs on Linux installed" prints
installed=trueand the two steps after it are skipped.
Don't merge before step 2. With the Dockerfile changed and base.json not, the runner's deploy stops: "services/runner/base has changed since base.json was written". The workflow and script changes on their own are safe on the old image, which installs as before.