Tauri Desktop Builds on GitHub Actions
Build Tauri v2 installers on GitHub Actions with a three-platform matrix, cached Rust and front-end state, and warp- runner sizes with per-minute rates.
Last verified:
A Tauri app that ships installers for Linux, Windows, and macOS needs a GitHub Actions matrix with one job per platform, because each platform links against its own system WebView and produces its own bundle formats. On WarpBuild that matrix is three runs-on labels, warp-ubuntu-latest-x64-8x, warp-windows-latest-x64-8x, and warp-macos-26-arm64-6x, with a Rust cache and a front-end cache on each leg and a separate cache action on Windows.
This page gives the per-platform dependency table, a matrix workflow with both caches and per-platform bundling, the runner sizes and rates for all three legs, the signing and notarization pointers per platform, and the list-price arithmetic against GitHub-hosted runners of the same shape.
Overview
WarpBuild provides Linux x64, Linux ARM64, macOS, and Windows runners, so all three legs of a Tauri release run on one account with one billing model. Moving an existing workflow across is a change to the runs-on line in each leg plus the cache steps.
A Tauri build is two builds stacked. The front end compiles to static assets through whatever bundler the project uses, and the Rust binary in src-tauri compiles against the platform's WebView bindings and then wraps those assets. The second half is what forces the matrix: webkit2gtk on Linux, WebView2 on Windows, and WKWebView on macOS are three different link targets, and the bundlers that produce the installers only run on their own platform.
The runner images carry the same tooling as GitHub-hosted runners, which covers the MSVC toolchain on Windows and the Xcode command line tools on macOS. Linux is the leg that needs an explicit install step, because the WebKitGTK development packages are not in the base image.
| Platform | Runner label | What the job installs | Bundle targets |
|---|---|---|---|
| Linux | warp-ubuntu-latest-x64-8x | libwebkit2gtk-4.1-dev, libayatana-appindicator3-dev, librsvg2-dev, libxdo-dev, libssl-dev, patchelf, build-essential | deb, rpm, AppImage |
| Windows | warp-windows-latest-x64-8x | Nothing at the OS level; the WiX and NSIS toolchains download into %LOCALAPPDATA%\tauri on first run | msi, nsis |
| macOS | warp-macos-26-arm64-6x | rustup target add aarch64-apple-darwin x86_64-apple-darwin for a universal binary | app, dmg |
Two version details are worth pinning. Tauri v2 links against webkit2gtk-4.1, while v1 linked against 4.0, so an Ubuntu 24.04 or 26.04 image and the 4.1 package name go together. And the macOS 26 labels ship the Xcode 27.0 SDKs and simulator runtimes while GitHub's upstream macOS 27 runner image is in beta, with a dedicated macOS 27 image to follow once that image is released, so pin DEVELOPER_DIR if the Rust build depends on a specific SDK.
One caching constraint shapes the workflow. The caching documentation states that WarpBuild Cache is not supported on Windows runners, and cache entries are versioned by paths and compression tool, so a macOS entry cannot restore on Linux anyway. Both facts push the same way: one cache step and one key per leg.
Configuration
The workflow below builds and bundles all three platforms from one matrix. The Linux and macOS legs use WarpBuilds/rust-cache, the Windows leg uses actions/cache over the same cargo directories, and every leg restores the front-end package manager store.
name: tauri-release
on:
push:
branches: [main]
pull_request:
jobs:
bundle:
name: bundle-${{ matrix.platform }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- platform: linux
runner: warp-ubuntu-latest-x64-8x
args: ""
- platform: windows
runner: warp-windows-latest-x64-8x
args: ""
- platform: macos
runner: warp-macos-26-arm64-6x
args: "--target universal-apple-darwin"
steps:
- uses: actions/checkout@v4
- uses: WarpBuilds/setup-node@v6
with:
node-version: 22
cache: pnpm
- run: rustup toolchain install stable --profile minimal
- name: Add Apple targets
if: matrix.platform == 'macos'
run: rustup target add aarch64-apple-darwin x86_64-apple-darwin
- name: Install WebKitGTK and bundler dependencies
if: matrix.platform == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \
librsvg2-dev libxdo-dev libssl-dev patchelf build-essential file
- name: Cache cargo state (Linux and macOS)
if: matrix.platform != 'windows'
uses: WarpBuilds/rust-cache@v2
with:
cache-provider: warpbuild
workspaces: "src-tauri -> target"
shared-key: tauri-${{ matrix.platform }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Cache cargo state (Windows)
if: matrix.platform == 'windows'
uses: actions/cache@v4
with:
path: |
~\.cargo\registry
~\.cargo\git
src-tauri\target
~\AppData\Local\tauri
key: windows-tauri-${{ hashFiles('src-tauri/Cargo.lock') }}
restore-keys: |
windows-tauri-
- run: pnpm install --frozen-lockfile
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
args: ${{ matrix.args }}
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.platform }}-bundles
path: src-tauri/target/**/release/bundle/**
if-no-files-found: errorFour details in that file are worth stating explicitly.
workspaces: "src-tauri -> target" points the cache action at the Cargo project, which lives one directory down in a Tauri repository rather than at the repository root. Without it the action finds no Cargo.toml and caches nothing, and the failure is silent apart from a full rebuild every run.
fail-fast: false keeps a Windows bundling failure from cancelling a macOS notarization that is already waiting in Apple's queue. One flaky leg otherwise costs the whole matrix run.
save-if limits cache writes to main, so pull request branches restore from the main-branch entry and skip the save. Combined with shared-key, that gives each platform one stable cache instead of one per run.
--target universal-apple-darwin is what produces a binary that runs on both Apple architectures, and it is the reason the macOS leg adds both rustup targets. Drop the flag and the bundle carries the runner's native architecture only.
Signing and notarization per platform
Signing is per platform and each half has its own guide. On macOS, APPLE_CERTIFICATE and APPLE_CERTIFICATE_PASSWORD import the Developer ID certificate into a keychain the job creates, and the APPLE_ID, APPLE_PASSWORD, and APPLE_TEAM_ID trio drives notarization through notarytool. The wait for Apple's verdict is wall-clock time on someone else's queue, and it is billed like any other minute: at $0.08 per minute on warp-macos-26-arm64-6x, a five minute notarization wait costs $0.40 per release run. The full keychain and credential sequence is in notarizing macOS apps in GitHub Actions.
On Windows, the msi and nsis bundles are signed through the signCommand entry in tauri.conf.json, which lets the certificate live in a cloud signing service rather than on the ephemeral runner. The certificate handling and timestamping steps are in code signing Windows binaries in GitHub Actions.
Linux bundles carry no OS-level signature. The key that matters there is TAURI_SIGNING_PRIVATE_KEY, which signs the updater manifest on every platform and has to be identical across the three legs, or clients reject the update.
Sizing
The catalog rows and per-minute rates for the three labels in the matrix, plus the sizes to move to, from the cloud runner catalog:
| Runner label | OS | vCPU | Memory | Storage | Price per minute |
|---|---|---|---|---|---|
| warp-ubuntu-latest-x64-8x | Ubuntu 24.04 | 8 | 32 GB | 150GB SSD | $0.016 |
| warp-ubuntu-latest-x64-16x | Ubuntu 24.04 | 16 | 64 GB | 150GB SSD | $0.032 |
| warp-windows-latest-x64-8x | Windows Server 2022 | 8 | 32 GB | 256GB SSD | $0.032 |
| warp-windows-latest-x64-16x | Windows Server 2022 | 16 | 64 GB | 256GB SSD | $0.064 |
| warp-macos-26-arm64-6x | macOS 26 | 6 | 22 GB | 120GB SSD | $0.08 |
| warp-macos-26-arm64-12x | macOS 26 | 12 | 44 GB | 270GB SSD | $0.16 |
Size each leg for the phase that dominates it. The Rust compile in src-tauri is the part that scales with cores, because cargo compiles independent crates in parallel, so a cold dependency graph rewards 16 vCPU on Linux and Windows. The front-end bundle walks its module graph on one thread before minifying in parallel, so it flattens out around 8 vCPU. Installer compression at the end is disk-bound with a mostly single-threaded compressor, and extra cores stop helping there.
The macOS leg is the one to watch for disk before speed. A universal build compiles the Rust side twice, once per architecture, and keeps both target directories plus the app, the dmg, and the notarization staging copy on the same volume. 120GB gets tight on a large workspace, and warp-macos-26-arm64-12x carries 270GB.
List prices against GitHub-hosted runners of the same shape
GitHub publishes per-minute list prices on the Actions minute multipliers reference and runner shapes on the GitHub-hosted runners reference. Both were checked on 2026-08-13.
warp-windows-latest-x64-8x (8 vCPU, 32 GB) costs $0.032 per minute against $0.042 per minute for the 8-core Windows larger runner (8 vCPU, 32 GB): 24 percent lower list price. The 6 vCPU macOS labels, including warp-macos-26-arm64-6x, cost $0.08 per minute against $0.102 per minute for the largest GitHub-hosted macOS ARM64 runner (5 vCPU, 14 GB): 22 percent lower list price, with one more vCPU and 8 GB more memory. GitHub list prices checked on 2026-08-13.
Worked monthly model
Take a desktop team whose release matrix runs 200 times a month across pull requests and tagged releases. Each run takes 11 minutes on the Linux leg, 15 minutes on the Windows leg, and 18 minutes on the macOS leg, with warm caches on all three and a universal build on macOS.
| Leg | Monthly minutes | WarpBuild rate | WarpBuild cost |
|---|---|---|---|
| Linux, 8 vCPU | 2,200 | $0.016 | $35.20 |
| Windows, 8 vCPU | 3,000 | $0.032 | $96.00 |
| macOS, 6 vCPU | 3,600 | $0.08 | $288.00 |
| Total | 8,800 | $419.20 |
On the two legs where GitHub publishes a same-shape list price, the same minutes come to $126.00 on the 8-core Windows larger runner and $367.20 on the largest GitHub-hosted macOS ARM64 runner, against $96.00 and $288.00 above: a $109.20 difference a month on this workload at list prices. Cache metering adds a little on top, since storage is $0.20 per GB-month and each cache write or restore is $0.0001, so a 6GB Tauri cache footprint with 4,000 operations a month comes to about $1.60.
Full rates for every size and platform are on the pricing page.
Bottlenecks
The cold Rust dependency graph. A Tauri app pulls in several hundred crates through the tauri and wry trees before any of your own code compiles. Cold, that is the single largest block of wall time in the pipeline, and it repeats on every leg. Key the cache on src-tauri/Cargo.lock so it refreshes exactly when dependencies change, and let restore-keys fall back to the newest previous entry so a lockfile bump starts from a mostly warm target.
The front end rebuilt three times. Every leg installs dependencies and runs the front-end build before the Rust side starts, so the same static assets are produced three times per release. Restoring the package manager store on each leg removes the install half. Where the front-end build takes long enough to matter, build it once in a separate job, upload the output with actions/upload-artifact, and have the three bundle legs download it.
The universal macOS build. Compiling both Apple architectures doubles the Rust half of the macOS leg, which is why that leg carries the longest minutes in the model above. Building only the native architecture on pull requests and reserving the universal build for tagged releases cuts the routine cost.
Notarization wait. Apple's verdict arrives when it arrives, and a larger runner does nothing for it. Keep notarization on the release path rather than on every pull request, and keep the whole signing sequence inside one step so keychain state cannot drift between steps.
The Windows leg with no WarpBuild Cache. That leg falls back to actions/cache, which has its own size limits, so cache only the cargo directories and the %LOCALAPPDATA%\tauri toolchain downloads rather than the whole workspace.
CI observability separates a CPU-bound cargo compile from a bundling step stuck on disk, and the Action Debugger pauses the workflow and opens an SSH session on the runner, which is the shortest path to inspecting a half-written bundle directory or a keychain in place.
Proof
Public OSS repositories running warp- labels are citable evidence, and the check is to open the workflow file and read the runs-on line. near/nearcore compiles the NEAR protocol node and runs its cargo-nextest suites on warp-ubuntu-2404-x64-16x and warp-ubuntu-2404-x64-8x, which is the same cargo-bound shape the Linux leg above has (checked on 2026-08-13). manaflow-ai/cmux shards a desktop app test suite across warp-macos-15-arm64-6x, the 6 vCPU macOS shape the macOS leg uses (checked on 2026-08-13).
Every cost number on this page carries its source and a checked-on date, so you can re-derive the arithmetic rather than take it. Rates for every size are on the pricing page.
Related reading: Electron app builds on GitHub Actions for the same three-platform shape with a Node runtime instead of a Rust one, fast Rust builds on GitHub Actions for cargo caching and sizing in depth, and the macOS notarization and Windows code signing guides for the signing halves.
FAQ
Does a Tauri release need one runner per platform?
Yes. Each platform links against its own system WebView and produces its own bundle formats, and the signing tools are per platform as well. The working shape is a three-leg matrix on warp-ubuntu-latest-x64-8x, warp-windows-latest-x64-8x, and warp-macos-26-arm64-6x.
What should a Tauri workflow cache?
Three things: the cargo registry and git checkouts, the src-tauri/target directory keyed on Cargo.lock, and the front-end package manager store keyed on the lockfile. Keep the Rust cache on WarpBuilds/rust-cache with cache-provider: warpbuild on the Linux and macOS legs, and use actions/cache on the Windows leg because WarpBuild Cache is not supported on Windows runners.
What runner sizes should each Tauri leg use?
Start at warp-ubuntu-latest-x64-8x at $0.016 per minute, warp-windows-latest-x64-8x at $0.032 per minute, and warp-macos-26-arm64-6x at $0.08 per minute. Move the macOS leg to warp-macos-26-arm64-12x at $0.16 per minute when a universal build fills the 120GB disk, since that label carries 270GB.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.