Taking Omarchy's nightly ISO build from 13 minutes to under 5
Two workflow changes cut Omarchy’s nightly ISO build to 4m43s: a faster runs-on runner and skipping compression on an already compressed artifact.
One of the things DHH has been obsessed with is making Omarchy install as fast as possible and honestly, getting it down to under one minute on some machines is really impressive.
But at runs-on, we couldn’t help but ask does it build fast on GitHub Actions too?
Here’s what we found out:
Omarchy does a simple nightly build#
The omarchy-iso ↗ repo has a nightly-build.yml ↗ that runs every night. It builds the complete Omarchy ISO and uploads it as a GitHub artifact which is ~6.2GB at the moment.
I ran the same job five times on ubuntu-latest runners in both a public and private repository and this what the timings looked like:
| Runner | Avg run time | Avg cost per run |
|---|---|---|
ubuntu-latest (public) | 12m44s | $0.00 |
ubuntu-latest (private) | 19m04s | ~$0.118 |
The difference you see here is because the ubuntu-latest machines on public repos have double the vCPU and RAM than those on private repos, while being free to use as well.
The job has 6 steps with 3 of them doing most of the work:
| Step | ubuntu-latest (public) | ubuntu-latest (private) |
|---|---|---|
| Free disk space | 1m40s | 2m22s |
| Build ISO | 7m57s | 13m09s |
| Upload artifact | 3m02s | 3m28s |
What’s the build actually doing?#
The Build ISO step starts an Arch Linux Docker container and runs the build-iso.sh ↗ script inside it.
That script installs the Arch ISO tooling, downloads roughly 1,250 packages from Arch, Omarchy and related repositories, and turns them into a local package repository before assembling the final ~6.2GB ISO.
That repository is included in the ISO so Omarchy can install the complete system without downloading packages from the internet.
The live filesystem is stored in a SquashFS image compressed with zstd. The package files are already zstd-compressed, so they are not compressed again inside SquashFS.
This gives us enough to optimise the workflow for performance:
1/ Change the runner machine#
The ubuntu-latest runner for public repositories has 4 vCPUs, 16GB of memory and a 14GB SSD, but 14GB is nowhere near enough for this build.
The offline Arch packages, Docker data, intermediate files and final ISO can all exist on disk at the same time which is why the workflow spends almost two minutes running a separate step that deletes preinstalled software from the runner.
This is also documented in this commit ↗.
So the first change was replacing ubuntu-latest with a runs-on runner:
runs-on: ubuntu-latestruns-on: runs-on=${{ github.run_id }}/family=m8azn.xlarge/image=ubuntu24-full-x64/volume=50gbubuntu-latest (public) | ubuntu-latest (private) | runs-on m8azn.large | runs-on m8azn.xlarge | |
|---|---|---|---|---|
| vCPUs | 4 | 2 | 2 | 4 |
| Memory | 16GB | 8GB | 8GB | 16GB |
| Root volume | Fixed 14GB SSD | Fixed 14GB SSD | Variable, 30GB minimum (50GB here) | Variable, 30GB minimum (50GB here) |
| PassMark score | 2,195–3,479 | 2,195–3,479 | 4,269 | 4,269 |
| Price/minute | Free | $0.006 | $0.001 Spot / $0.003 on-demand | $0.003 Spot / $0.007 on-demand |
The m8azn family is the best general-purpose compute AWS currently offers. The large matches the 2 vCPUs and 8GB of memory in GitHub’s private runner, while the xlarge matches the public runner’s 4 vCPUs and 16GB.
Both runs-on configurations use a newer, faster CPU and let us choose the volume size in the label itself.
Which means we can get rid of this step:
name: Free Disk Spaceuses: jlumbroso/free-disk-space@v1.3.1After running the migrated workflow, we can observe the following improvements in both run time and cost:
| Runner | Avg run time | Build ISO | Upload artifact | Avg cost On-Demand | Avg cost Spot |
|---|---|---|---|---|---|
runs-on m8azn.large | 7m21s ↓61% | 5m36s ↓57% | 1m41s ↓51% | ~$0.0225 ↓81% | ~$0.0081 ↓93% |
runs-on m8azn.xlarge | 5m48s ↓54% | 4m05s ↓49% | 1m39s ↓46% | ~$0.0417 ↓65% | ~$0.0177 ↓85% |
RunsOn costs include runner compute and the 50GB gp3 volume.
2/ Stop compressing an already compressed ISO#
The final significant step uploads the 6.2GB ISO to GitHub and actions/upload-artifact compresses uploads at level 6 by default ↗.
But the data being uploaded here already looks like this:
GitHub artifact compression└── Omarchy ISO └── zstd-compressed SquashFS filesystem └── individually compressed Arch packagesSo compressing it again saved almost no space but consumed CPU and added more than a minute to the upload.
So we just changed the the GitHub compression level to 0 here:
- name: Upload newly built ISO artifact uses: actions/upload-artifact@v4 with: name: omarchy-iso-${{ github.run_number }} path: ./release/*.iso retention-days: 1 compression-level: 0This changes only the compression used by GitHub while uploading the artifact and does not change the SquashFS settings or anything inside the Omarchy ISO
In the measured runs, the artifact grew by only around 0.6%, while upload times dropped substantially:
| Runner | Avg run time | Build ISO | Upload artifact | Avg cost On-Demand | Avg cost Spot |
|---|---|---|---|---|---|
runs-on m8azn.large | 5m55s ↓69% | 5m18s ↓60% | 33s ↓84% | ~$0.0180 ↓85% | ~$0.0066 ↓94% |
runs-on m8azn.xlarge | 4m43s ↓63% | 4m06s ↓48% | 32s ↓82% | ~$0.0338 ↓71% | ~$0.0144 ↓88% |
The final workflow#
The resulting job is still very close to the original but with a better right-sized machine and 0 compression:
jobs: build-iso: runs-on: runs-on=${{ github.run_id }}/family=m8azn.xlarge/image=ubuntu24-full-x64/volume=50gb
steps: - name: Checkout repository uses: actions/checkout@v4
- name: Build ISO run: NO_BOOT_OFFER=1 ./bin/omarchy-iso-make
- name: Delete previously uploaded ISO artifact uses: philips-labs/action-delete-artifacts@v1.0.0 with: workflow: nightly-build.yml continue-on-error: true
- name: Upload newly built ISO artifact uses: actions/upload-artifact@v4 with: name: omarchy-iso-${{ github.run_number }} path: ./release/*.iso retention-days: 1 compression-level: 0Across the measured Spot and on-demand runs, m8azn.xlarge averaged 4m43s: a 63% reduction in run time versus the public ubuntu-latest runner.
The m8azn.large averaged 5m55s: a 69% reduction in run time versus the private ubuntu-latest runner. On-demand cost fell by 85% per run, with Spot increasing the saving to 94%.
There was no single complicated optimisation here.
I inspected what the workflow was actually doing and gave it the resources it needed and stopped compressing an already compressed 6.2GB file.