Cross-platform deploy: macOS + Windows + Linux
Deploying a Tauri desktop application across three different operating systems is not a simple "build and ship". Each platform has its own quirks: code signing certificates, CPU architectures, installers, and system permissions. In this article, you'll discover the complete procedure to package, sign, and distribute your Rust app on macOS, Windows, and Linux, avoiding the common gotchas that block production deploys.
What You'll Learn
- Build requirements for each platform (SDKs, certificates, architectures)
- tauri.conf.json configuration for multi-OS bundling
- macOS code signing with Apple Developer certificates
- macOS notarization to bypass Gatekeeper
- Extended Validation (EV) signing on Windows
- Creating and distributing AppImage/deb on Linux
- Cross-platform CI/CD with GitHub Actions
- Testing bundles before production release
Tauri deploy architecture
Tauri compiles your Rust app + web frontend into native bundles: .app for macOS, .exe/.msi for Windows, AppImage/deb for Linux. The tauri.conf.json file is your central control point: configure here the ID, icons, certificates, and bundle strategies for each OS.
Build prerequisites for each OS
Before starting cross-compilation, ensure you have the right tools installed:
// Verify prerequisites (macOS host)
rustc --version # Rust 1.70+
cargo --version
xcode-select --install # Xcode Command Line Tools
cargo install tauri-cli # or: npm install -D @tauri-apps/cli@next
// For Windows target: install MSVC toolchain
rustup target add x86_64-pc-windows-msvc
// For Linux target (from macOS):
rustup target add x86_64-unknown-linux-gnu
brew install mingw-w64 # Cross-compiler
Build folder structure
After the first build, Tauri generates a src-tauri/ folder with the Rust structure, a cargo.toml, and native configuration. Final outputs go to src-tauri/target/release/bundle/{os}/
tauri.conf.json configuration
The tauri.conf.json file is at the heart of configuration. Here we define the unique ID, version, bundle strategy, and OS-specific settings like certificates and notarization.
{
"productName": "MyRustApp",
"version": "1.0.0",
"identifier": "com.company.myapp",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:5173",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "My Rust App",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
]
},
"bundle": {
"active": true,
"targets": [
"deb", // Linux
"app", // macOS
"msi", // Windows
"dmg" // macOS disk image
],
"deb": {
"depends": ["libssl3", "libssl-dev"],
"desktop": "MyRustApp"
},
"macOS": {
"frameworks": ["Security", "CoreFoundation"],
"minimumSystemVersion": "11.0",
"exceptionDomain": "com.company.myapp"
},
"windows": {
"certificateThumbprint": null,
"signingIdentity": null,
"timestampUrl": "http://timestamp.comodoca.com/authenticode"
}
}
}
Code Signing macOS
On macOS, every app must be signed with a valid certificate from your Apple Developer Account. Without a signature, Gatekeeper blocks execution. Signing is also required for notarization.
Obtaining certificates
Visit developer.apple.com, go to Certificates, Identifiers & Profiles. Create a Developer Certificate (or a Distribution Certificate for public release). Download the .cer and install it in Keychain.
// Export the certificate as .p12 from Keychain
security export-idents -t all -f pkcs12 \
-o ~/myapp-cert.p12 \
-k ~/Library/Keychains/login.keychain-db \
"Developer ID Application: My Name"
// You'll be prompted for your Keychain password.
// Now you have myapp-cert.p12 — keep it private!
Configuring signing in Tauri
Tauri auto-detects certificates from macOS Keychain. If you want to be explicit, modify tauri.conf.json:
"bundle": {
"macOS": {
"signingIdentity": "Developer ID Application: My Name (TEAM_ID)",
"certificateFile": null, // Or path to .p12 if you prefer
"certificatePassword": null // Env var: TAURI_SIGNING_PRIVATE_KEY
}
}
For CI/CD, export the identity to an env var and use Tauri CLI:
// GitHub Actions: sign macOS
- name: Import signing certificate
run: |
echo "$APPLE_CERTIFICATE" | base64 --decode > cert.p12
security import cert.p12 -k ~/Library/Keychains/login.keychain-db \
-P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
env:
APPLE_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
- name: Build and sign
run: npm run tauri build -- --target universal-apple-darwin
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
Notarization macOS
On macOS 10.15+, Gatekeeper requires notarization even for signed apps. Notarization is Apple's automated verification of the app for malware — a process that takes a few minutes.
Obtaining an App Password
On appleid.apple.com, generate an App Password dedicated to notarization (one per app is good practice).
Notarizing the DMG
Tauri generates the DMG after the build. Notarize it with xcrun notarytool:
#!/bin/bash
# notarize.sh
APP_PATH="src-tauri/target/release/bundle/macos/MyRustApp.app"
DMG_PATH="src-tauri/target/release/bundle/dmg/MyRustApp_1.0.0_x64.dmg"
# Submit for notarization
xcrun notarytool submit "$DMG_PATH" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$TEAM_ID" \
--wait
// If notarization passes, staple the ticket to the app
xcrun stapler staple "$APP_PATH"
echo "Notarization complete! App is ready for macOS."
Code Signing Windows
Windows requires EV (Extended Validation) signing to avoid the "publisher unknown" SmartScreen warning. An EV certificate costs ~$300–400/year but is necessary for professional distribution.
Obtaining an EV certificate
Purchase from a certificate provider (Sectigo, DigiCert, GlobalSign). The certificate is issued as .pfx. Install on your Windows build machine and configure Tauri:
"bundle": {
"windows": {
"certificateThumbprint": "ABCD1234...", // Certificate thumbprint
"signingIdentity": null,
"timestampUrl": "http://timestamp.comodoca.com/authenticode"
}
}
Tauri will use the certificate from Windows Certificate Store. For CI/CD, import the .pfx:
// PowerShell: import certificate
Import-PfxCertificate `
-FilePath "C:\cert.pfx" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Password (ConvertTo-SecureString -String $env:CERT_PASSWORD -AsPlainText -Force)
Bundle Linux: AppImage and Debian
On Linux, Tauri generates AppImage (standalone executable) and .deb packages. AppImage is the most portable format; .deb is for Debian-based distros (Ubuntu, Debian).
AppImage: portable distribution
AppImage requires no installation. The user downloads, makes executable, and runs. The file is self-contained and works on any modern Linux.
// AppImage configuration in tauri.conf.json
"bundle": {
"linux": {
"appimage": {
"bundleAppimage": true
}
}
}
Debian package
The .deb integrates the app into the Linux package manager. The user installs with apt and receives automatic updates.
// .deb configuration
"bundle": {
"deb": {
"depends": ["libssl3", "libgtk-3-0"],
"desktop": "MyRustApp",
"section": "Utility"
}
}
Cross-Platform CI/CD with GitHub Actions
Automating the build for all three platforms on every commit is the gold standard. GitHub Actions provides runners for macOS, Windows, and Linux.
name: Build Multi-Platform
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
strategy:
matrix:
include:
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Build frontend
run: npm install && npm run build
- name: Install Tauri CLI
run: npm install -D @tauri-apps/cli@latest
- name: Build and bundle (macOS)
if: runner.os == 'macOS'
run: npm run tauri build -- --target universal-apple-darwin
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
- name: Build and bundle (Windows)
if: runner.os == 'Windows'
run: npm run tauri build
env:
TAURI_WINDOWS_SIGN_THUMBPRINT: ${{ secrets.WINDOWS_CERT_THUMBPRINT }}
- name: Build and bundle (Linux)
if: runner.os == 'Linux'
run: npm run tauri build
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: bundles-${{ matrix.os }}
path: src-tauri/target/release/bundle/
Testing Bundles
Before releasing, test the bundle in a real environment. Install the app and verify: system permissions, update checker, crash handling, and UI consistency across platforms.
Pre-release checklist
- App launches without errors from zero
- Signatures and certificates are valid (check Info.plist on macOS)
- Icons and assets are correct for each platform
- Update checker works (if implemented via Tauri updater)
- Crash logging works (Sentry, Datadog, or custom)
- Performance: startup time < 2s
Debugging signing errors
If the bundle doesn't start after signing, check:
// macOS: verify signing and notarization
codesign -v "/path/to/MyRustApp.app"
xcrun stapler validate "/path/to/MyRustApp.app"
// Windows: check certificate thumbprint in certificate store
Get-ChildItem -Path "Cert:\CurrentUser\My" | Select Thumbprint, Subject
// Linux: AppImage is always executable
chmod +x MyRustApp_1.0.0_x64.AppImage
./MyRustApp_1.0.0_x64.AppImage --version
Distribution and Updates
Once the bundle is signed and tested, distribute it via:
- GitHub Releases: upload the .dmg, .exe, .AppImage and signature assets
- Tauri Updater: integrate Tauri's auto-update system in-app (downloads update in background)
- App Store / Microsoft Store: official distribution on first-party platforms (extra compliance required)
- Website: landing page with direct download links per OS
FAQ
Can I cross-compile on macOS for Windows/Linux without a Windows VM?
Yes, rustup target add x86_64-pc-windows-msvc + mingw-w64 lets you compile for Windows on macOS. However, Windows code signing still requires native MSVC toolchain or a Windows VM. For CI/CD, use separate GitHub Actions runners for each OS.
Is an EV certificate on Windows really necessary?
No, but without it the user will see a "publisher unknown" SmartScreen warning on first launch. If your app is for a trusted community (open source, personal project), you can skip the EV certificate. For companies and commercial distribution, it's strongly recommended.
How do I manage different versions per OS (e.g., 1.0.1 for macOS only)?
Define versioning in tauri.conf.json once (single source of truth). If you have urgent OS-specific fixes, use Rust feature flags to compile different variants, or maintain separate CI/CD branches with versioning via git tags (v1.0.1-macos, v1.0.1-windows).
What happens if the macOS certificate expires?
Users who have already downloaded the app are unaffected (the signature is valid). New apps that users download after expiration will fail Gatekeeper verification. Renew the certificate before expiry and rebuild + notarize all apps.
Can I use self-signed certificates for testing?
On macOS and Windows, no — Gatekeeper/SmartScreen will reject the certificate. For local testing, temporarily disable signing (dev builds only, never in production).
In summary: cross-platform deploy on Tauri requires attention to certificates, notarization, and CI/CD automation. Once you configure the flow once, releasing becomes routine and predictable.

Commenti
Caricamento commenti...
Sign in to comment