tauri-bundling
Use when packaging a Tauri v2 app for release — building per-OS installers (dmg, msi, nsis, appimage, deb, rpm), code signing (macOS notarization + Apple Developer ID, Windows Authenticode, Linux AppImage signing), store submission (App Store, MS Store, Google Play), or wiring a GitHub Actions release pipeline.
Tauri v2 Bundling & Distribution
A hub skill for shipping a Tauri v2 app to real users. Covers the build pipeline, every supported per-OS installer format, the code-signing requirements that gate “no scary warnings on first launch,” store submission paths, and a working CI workflow. For mobile (iOS / Android) only the high-level submission path is here — driver-level mobile setup belongs in a separate skill.
Two templates ship with this skill:
templates/github-actions-release.yml— multi-OS matrix usingtauri-apps/tauri-action.templates/tauri.conf.bundle.json— production bundle config with identifier, publisher, and signing toggles per OS.
1. Build basics
The single entry point is tauri build. Defaults to every configured bundle on the host OS.
# Build everything configured for the host OS
bun run tauri build # or npm / pnpm / yarn / cargo tauri build
# Build only specific formats
bun run tauri build --bundles dmg,app # macOS, no Universal target
bun run tauri build --bundles msi,nsis # Windows
bun run tauri build --bundles appimage,deb,rpm # Linux
# Build the binary only — skip bundling. Lets you wrap your own installer.
bun run tauri build --no-bundle
Output artifacts land in src-tauri/target/release/bundle/<format>/. Each bundle subdir holds the
final installer file plus, for code-signed formats, the signature sidecar.
Versioning. Pulled from tauri.conf.json > version. If absent, falls back to Cargo.toml > package.version. Mobile platforms compute an integer version code from semver — see Mobile section.
Bundle identifier. tauri.conf.json > identifier (e.g. com.acme.maximal) must:
- Be globally unique reverse-DNS
- Match the App Store / Play Store / Microsoft Store record exactly
- Not change after first release — auto-update and signing both key off it
2. macOS — .app, .dmg, App Store
.app bundle
Layout produced by tauri build --bundles app:
<productName>.app/Contents/
├── Info.plist
├── MacOS/<app-name> # the actual executable
├── Resources/ # icon.icns + bundled assets
├── _CodeSignature/
├── Frameworks/
├── PlugIns/
└── SharedSupport/
Relevant tauri.conf.json > bundle.macOS keys:
minimumSystemVersion— default10.13. Bump cautiously; users on older macOS will silently fail to launch.frameworks— system or vendored frameworks to embed.files— arbitrary file map into the bundle.entitlements— path to yourEntitlements.plist.
To extend Info.plist, drop a custom Info.plist next to tauri.conf.json in src-tauri/. Tauri
merges your keys over its generated ones.
$PATHgotcha. GUI macOS apps do not inherit a login-shell$PATH. If you shell out (e.g. togit,bun,node), use thefix-path-envcrate or hardcode absolute paths.
.dmg installer
tauri build --bundles dmg wraps the .app in the standard drag-to-Applications window. Configure
under bundle.macOS.dmg:
"dmg": {
"background": "./assets/dmg-background.png",
"windowSize": { "width": 660, "height": 400 },
"windowPosition": { "x": 200, "y": 120 },
"appPosition": { "x": 180, "y": 170 },
"applicationFolderPosition":{ "x": 480, "y": 170 }
}
CI gotcha: DMG icon positioning is flaky on headless runners (no WindowServer). See tauri#1731. If positions look wrong only on CI, build the DMG locally or skip the visual tweaks.
App Store (.pkg)
Hard requirements:
- Apple Developer Program ($99/yr) — free tier cannot notarize.
- App Sandbox capability — required by App Review. Put in your
Entitlements.plist. - Bundle ID matches App Store Connect record exactly.
- Universal binary. Build with
tauri build --bundles app --target universal-apple-darwin. - Mac App Store Provisioning Profile referenced in config.
- Encryption compliance key in
Info.plist(ITSAppUsesNonExemptEncryption).
Workflow:
tauri build --bundles app --target universal-apple-darwin # universal .app
xcrun productbuild --component MyApp.app /Applications \
--sign "3rd Party Mac Developer Installer: ACME (TEAMID)" \
MyApp.pkg # signed .pkg
xcrun altool --upload-app -f MyApp.pkg \
--apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID" # upload
App Store Connect API keys live as AuthKey_<KEY_ID>.p8 in ~/.appstoreconnect/private_keys/ or
./private_keys/.
Code signing (macOS)
Two cert flavors:
- Developer ID Application — direct distribution (DMG, zip). Requires notarization.
- Apple Distribution — App Store only.
Find your installed identity:
security find-identity -v -p codesigning
Local signing — set in tauri.conf.json:
"macOS": { "signingIdentity": "Developer ID Application: ACME (TEAMID)" }
…or via env: APPLE_SIGNING_IDENTITY="Developer ID Application: ACME (TEAMID)".
CI signing — export a .p12 and base64 it:
base64 -i Certificates.p12 | pbcopy
Then in CI secrets:
APPLE_CERTIFICATE— base64 of the.p12APPLE_CERTIFICATE_PASSWORD—.p12passwordAPPLE_SIGNING_IDENTITY— the human-readable cert name
tauri-action imports the cert into a temporary keychain automatically when these are present.
Notarization (required for Developer ID Application). Pick one auth method:
API key (preferred for CI):
APPLE_API_ISSUER— issuer UUIDAPPLE_API_KEY— key IDAPPLE_API_KEY_PATH— path to the.p8file (orAPPLE_API_KEY_BASE64)
Or Apple ID:
APPLE_ID— emailAPPLE_PASSWORD— app-specific password (not your iCloud password)APPLE_TEAM_ID— 10-char team ID
Tauri auto-runs xcrun notarytool submit --wait then xcrun stapler staple when these are set.
Manual equivalent:
xcrun notarytool submit MyApp.dmg --apple-id … --password … --team-id … --wait
xcrun stapler staple MyApp.dmg
Hardened runtime is on by default for Developer ID signing. Add entitlements you actually use —
com.apple.security.cs.allow-jit, com.apple.security.cs.disable-library-validation etc. —
sparingly.
Ad-hoc signing (no Apple account, dev only on Apple Silicon): set signingIdentity: "-". Users
will still see the Gatekeeper warning.
3. Windows — MSI, NSIS, Microsoft Store
Two installer formats
| Format | Backend | Pros | Cons |
|---|---|---|---|
| MSI | WiX Toolset v3 | Group Policy / enterprise deploy, native MSI tooling | Windows-only build host, requires VBSCRIPT optional feature |
| NSIS | NSIS | Cross-compile from Linux/macOS, scriptable hooks, smaller | Less enterprise-friendly |
Default with tauri build on Windows is MSI + NSIS. Pick one with --bundles msi or --bundles nsis. Most projects ship NSIS for direct downloads and MSI only if they need Active Directory
deployment.
Cross-compile from non-Windows (NSIS only)
# Linux: apt install nsis llvm lld
# macOS: brew install nsis llvm
rustup target add x86_64-pc-windows-msvc
cargo install cargo-xwin
tauri build --bundles nsis --target x86_64-pc-windows-msvc --runner cargo-xwin
WebView2 distribution
WebView2 is required at runtime. Pick a strategy under bundle.windows.webviewInstallMode:
| Mode | Net required | Installer size delta | When |
|---|---|---|---|
downloadBootstrapper | Yes (on install) | 0 MB | Default. Skip for Win7 MSIs. |
embedBootstrapper | Yes (on install) | +1.8 MB | Better Win7 support. |
offlineInstaller | No | +127 MB | Offline / air-gapped customers. Required for MS Store. |
fixedVersion | No | +180 MB | Pin a specific WebView2 build (regulated envs). |
Customization
- WiX (MSI): custom XML fragments via
bundle.windows.wix.fragmentPaths, custom templates viawix.template, localization viawix.language. - NSIS: lifecycle hooks (
preinstall,postinstall,preuninstall,postuninstall) viabundle.windows.nsis.installerHooks. Far cleaner than full template replacement for dropping in dependencies or registry keys.
Code signing (Windows / Authenticode)
Signed installers avoid SmartScreen warnings — but reputation accrues per cert:
- EV cert — instant SmartScreen reputation, hardware token required.
- OV cert — cheaper, but SmartScreen warns until enough installs accumulate.
Three signing backends, in increasing order of CI-friendliness:
1. Local .pfx with signtool (OV legacy path). Import the .pfx into the Windows keystore,
then in tauri.conf.json:
"windows": {
"certificateThumbprint": "AABBCC…",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
}
2. Azure Key Vault via the relic tool. Vault-stored cert, secret-based auth
(AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET). Configure a custom sign command under
bundle.windows.signCommand.
3. Azure Trusted Signing (recommended for new projects) via trusted-signing-cli.
Microsoft-managed cert, per-signature billing, no hardware token. Requires .NET 6+ and signtool on
the runner.
For cross-platform CI, osslsigncode can sign Windows installers from Linux/macOS — useful when
matrix runners are Ubuntu.
Microsoft Store
Tauri does not produce MSIX directly. The Store accepts EXE/MSI via “EXE or MSI app” registration, but:
- Installer must be offline — set
webviewInstallModetoofflineInstaller. - Installer must be code-signed.
- Installer must handle its own auto-updates.
- Publisher name ≠ product name — set
bundle.publisherexplicitly if yourproductNameand identifier share a token.
Use a separate tauri.microsoftstore.conf.json overlay to keep the Store-specific WebView2 setting
out of your normal release build:
tauri build --config tauri.microsoftstore.conf.json --bundles msi
4. Linux — AppImage, deb, rpm, Flatpak, Snap, AUR
Pick a format
| Format | Use when | Avoid when |
|---|---|---|
| AppImage | You want one portable file, no install step, broad distro support | You care about installer size (70+ MB vs 2–6 MB) or need PATH integration |
| deb | Ubuntu/Debian/Mint users via apt | You need to target Fedora/RHEL |
| rpm | Fedora/RHEL/openSUSE users via dnf | Debian-family users |
| Flatpak | Universal sandboxed install via Flathub | You need raw filesystem access |
| Snap | Ubuntu first-party store presence | Users dislike snapd overhead |
| AUR | Arch users — community-maintained | Anyone but Arch |
The glibc baseline problem (read this first)
“You must build your Tauri application using the oldest base system you intend to support that also provides Tauri v2’s required WebKitGTK 4.1 packages.”
Build on Ubuntu 22.04 / Debian 12 (or older with backports). Building on Ubuntu 24.04 will silently
raise your minimum glibc and break 22.04 users at runtime. In CI: use a container or pin
ubuntu-22.04 runners. Never ubuntu-latest for shipping Linux builds.
System deps for the runner:
sudo apt-get install -y \
libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
AppImage
tauri build --bundles appimage
Configure under bundle.linux.appimage:
bundleMediaFramework: true— pulls GStreamer for<audio>/<video>(large size hit).files— extra files, paths must start with/usr/.
ARM AppImages cannot cross-compile. Build on ARM hardware or ARM GitHub runner
(ubuntu-22.04-arm).
GPG signing:
gpg2 --full-gen-key # one-time
export SIGN=1
export SIGN_KEY=<KEY_ID> # optional, else default key
export APPIMAGETOOL_SIGN_PASSPHRASE=<…>
export APPIMAGETOOL_FORCE_SIGN=1 # fail the build if signing fails
tauri build --bundles appimage
AppImage itself does not validate signatures at launch. Users must run the
validatetool from AppImageUpdate releases, and you must publish the key fingerprint somewhere over TLS. Treat AppImage signing as integrity attestation, not active enforcement.
Debian (.deb)
tauri build --bundles deb
Auto-generated control fields list dependencies libwebkit2gtk-4.1-0, libgtk-3-0, and (when tray
enabled) libappindicator3-1. Add extra Depends: via bundle.linux.deb.depends. Map extra files
via bundle.linux.deb.files. Same glibc warning applies — build on the oldest target.
ARM cross-compile is supported (the docs detail ARMv7 and ARM64 targets, linker overrides, sysroot setup).
RPM (.rpm)
tauri build --bundles rpm
Configure under bundle.linux.rpm:
depends,conflicts,provides,obsoletes— standard RPM relations.preInstallScript,postInstallScript,preRemoveScript,postRemoveScript— shell scripts placed insrc-tauri/scripts/.epoch,release— version metadata.
RPM signing uses GPG. Export your public key, import into the RPM DB, configure ~/.rpmmacros:
%_signature gpg
%_gpg_name YOUR_KEY_ID
Then build — rpmbuild signs automatically. Verify with rpm -K MyApp.rpm.
Inspect a built RPM: rpm -qip (header), rpm -qp --scripts (lifecycle scripts), rpm -ivh -vv
(verbose install).
Flatpak
Build a .deb first, then wrap it with flatpak-builder using a manifest that:
- Targets GNOME runtime/SDK 46 (covers Tauri’s deps).
- Declares permissions (
--socket=wayland,--socket=x11,--device=dri). - Extracts the
.deb, copies binary + resources to/app, registers desktop file + icons + AppStream MetaInfo.
Submitting to Flathub: fork flathub/flathub, branch off, add your manifest, PR against the
new-pr branch. After acceptance you get a per-app repo for ongoing updates.
Snapcraft
Author a snapcraft.yaml with:
base: core22,confinement: strict,grade: stableextensions: [gnome]— pulls desktop/wayland/x11 plugs in one linebuild-packagesfor Rust + webkit2gtk-devstage-packagesfor runtime libsapps.<name>.commandpointing at the installed binary
Publish:
snapcraft login
snapcraft pack
snapcraft upload --release=stable myapp_1.0.0_amd64.snap
Register the snap name at snapcraft.io first.
AUR
For Arch, write a PKGBUILD. Two flavors:
-bin— repackages your.debrelease artifact. Faster for users, less maintenance.- From source — pulls a git tag and runs
cargo tauri build. Standard for community AUR.
Required fields: pkgname, pkgver, pkgrel, pkgdesc, arch, url, license, depends,
source, sha256sums. Generate .SRCINFO with makepkg --printsrcinfo > .SRCINFO, test with
makepkg, push to ssh://aur@aur.archlinux.org/<pkgname>.git.
5. Mobile — high level only
iOS / App Store
bun run tauri ios build --export-method app-store-connect
Required:
- Apple Developer enrollment + Mac for signing.
- Bundle ID registered in App Store Connect, matching
identifier. - Automatic signing (recommended) — set
APPLE_API_ISSUER,APPLE_API_KEY,APPLE_API_KEY_PATH. Xcode handles certs + provisioning. - Manual signing — base64 your
.p12and.mobileprovision, setIOS_CERTIFICATE,IOS_CERTIFICATE_PASSWORD,IOS_MOBILE_PROVISION.
The generated .ipa uploads via xcrun altool to App Store Connect. TestFlight pickup is automatic
after processing.
Android / Google Play
bun run tauri android build -- --aab
Pre-step: create an upload keystore.
keytool -genkey -v -keystore ~/upload-keystore.jks \
-keyalg RSA -keysize 2048 -validity 10000 -alias upload
Then src-tauri/gen/android/keystore.properties (do not check in):
password=…
keyAlias=upload
storeFile=/abs/path/to/upload-keystore.jks
Wire it into src-tauri/gen/android/app/build.gradle.kts with the signingConfigs { release { … } } block reading the properties file, then signingConfig = signingConfigs.getByName("release") on
the release build type.
Output: gen/android/app/build/outputs/bundle/universalRelease/app-universal-release.aab. Version
code is auto-computed as major*1_000_000 + minor*1_000 + patch.
First Play Console upload must be manual — Google verifies your signing cert + bundle ID. Subsequent uploads can be automated via the Play Developer API (no built-in Tauri integration yet).
6. GitHub Actions release pipeline
See templates/github-actions-release.yml for a complete file. The shape:
- Triggers on push to
release(or a tag). - Matrix over
macos-latest(aarch64 + x86_64),ubuntu-22.04(x64),ubuntu-22.04-arm,windows-latest. - Per-OS steps: install system deps (Linux), set up Node + Rust + Rust cache, install frontend deps.
- One
tauri-apps/tauri-action@v0step does build + bundle + GitHub Release upload. - Secrets passed via
env:— signing certs, notarization creds, Tauri updater signing key.
Minimum required secrets:
- macOS:
APPLE_CERTIFICATE,APPLE_CERTIFICATE_PASSWORD,APPLE_SIGNING_IDENTITY, plus either (APPLE_API_ISSUER+APPLE_API_KEY_ID+APPLE_API_KEY) or (APPLE_ID+APPLE_PASSWORD+APPLE_TEAM_ID) for notarization. - Windows: depends on signing backend —
WINDOWS_CERTIFICATE+WINDOWS_CERTIFICATE_PASSWORD(PFX), or Azure creds for Trusted Signing. - Tauri updater (if used):
TAURI_SIGNING_PRIVATE_KEY,TAURI_SIGNING_PRIVATE_KEY_PASSWORD.
The GITHUB_TOKEN is auto-issued, but the workflow needs permissions: contents: write to publish
a Release. If you previously locked workflow permissions to read-only repo-wide, override per-job.
ARM Linux on GitHub:
ubuntu-22.04-armrunners are now GA for public repos. Private repos may still need self-hosted ARM runners — emulated builds take ~1h and routinely flake.
7. Common gotchas
- Universal vs single-arch macOS. A non-universal
.appruns on either Intel or Apple Silicon, not both. App Store rejects single-arch unless yourLSMinimumSystemVersionpredates Apple Silicon (it doesn’t). codesign --deepis deprecated. Tauri signs nested binaries top-down in the right order. If you patch-sign manually post-bundle, use--options runtimeand sign innermost-first.- Notarization staple step is separate. Submission succeeds but the ticket isn’t embedded until
you
stapler staple. Without it, offline users see “Apple could not verify…” until they connect. - MSI vs NSIS — when in doubt, ship NSIS. Smaller, scriptable, cross-compilable, no VBSCRIPT requirement. Only ship MSI when an actual customer has asked for it.
- AppImage glibc baseline. Build on the oldest WebKitGTK-4.1-capable distro you support. There is no way to “lower” glibc after the fact short of rebuilding.
tauri build≠cargo build --release. Bundle steps run after compile. A greencargo builddoes not mean your installer will build — bundler errors surface late.- Identifier collisions. Changing
identifierbetween releases breaks auto-update and macOS keychain access. Pick once, keep forever. - Publisher name = product name. Microsoft Store rejects this. Always set
bundle.publisherexplicitly. - DMG icon positions on CI. No WindowServer means
osascriptwindow tweaks silently fail. Build the prettified DMG locally if visual layout matters; ship a plain one from CI otherwise. tauri ios buildon Linux/Windows. Not supported. macOS host only. Run mobile builds on a separate matrix job pinned tomacos-latest.
Templates
templates/github-actions-release.yml— drop into.github/workflows/release.yml. Fill in secrets, swapbunfor your package manager if needed.templates/tauri.conf.bundle.json— merge under yourtauri.conf.json > bundlekey. Has placeholder identifier, publisher, per-OS signing toggles, and WebView2 + DMG defaults that match what this skill recommends.