CI/CD for a Zig Build Project with GitHub Actions
Install a pinned Zig, run zig build test, and cross-compile release binaries on a tag.
This recipe builds a Zig project with the standard build system. CI installs a pinned Zig version, runs zig build test, and on a tag cross-compiles release binaries for several targets using Zig's built-in cross toolchain.
What the pipeline does
- install a pinned Zig version
- run zig build test
- build with -Doptimize=ReleaseSafe
- on a tag, cross-compile a target matrix
- upload binaries as artifacts or release assets
The workflow
mlugg/setup-zig installs and caches Zig. zig build supports -Dtarget for cross-compilation without external toolchains, so one runner builds every target.
name: CI
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.13.0
- run: zig build test
- run: zig build -Doptimize=ReleaseSafe
release:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
matrix:
target: [x86_64-linux, aarch64-linux, x86_64-macos, aarch64-macos]
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.13.0
- run: zig build -Doptimize=ReleaseSafe -Dtarget=${{ matrix.target }} --prefix dist
- uses: softprops/action-gh-release@v2
with:
files: dist/bin/*Caching and speed
setup-zig caches the Zig compiler and the global cache (~/.cache/zig). Cross-compiling four targets is CPU bound; cheaper managed runners such as Latchkey $0.0025/min at 2 vCPU against $0.006 GitHub-hosted cut release time and auto-retry a flaky compiler download.
Release output
Pin the exact Zig version since the language and build API still change between releases. zig build -Dtarget cross-compiles from a single Linux runner, and action-gh-release attaches each target's binary to the tag release.
Making this reliable in CI
- Pin every tool version. An unpinned toolchain turns a runner image update into a build break on unchanged code.
- Cache what is expensive to produce, and key the cache to the tool version so a restore across a version boundary cannot poison the build.
- Assert on the produced artifact rather than on the command succeeding.
- Set an explicit timeout so a hung step fails fast instead of consuming the whole job budget.
Key takeaways
- Pin the Zig version exactly since the build API still evolves.
- Use -Dtarget to cross-compile every target from one runner.
- Run zig build test on PRs before any release build.