Distributing Your Application
Package, sign, and share your Tauri v2 app with users on Windows, macOS, and Linux using installers, portable builds, website hosting, and GitHub Releases.
Distributing a Tauri application means turning the code you built into something a non‑developer can install or run on their own machine. That involves picking the right package format, signing the binaries so the operating system trusts them, and putting the final files where users can download them.
This section covers the entire flow — from a successful tauri build to a download link that works on Windows, macOS, and Linux. It assumes you already have a Tauri v2 project with a React + Vite frontend and that you can run the build command. If that command still fails, revisit Building a Tauri Application before continuing here. Signing is covered in Code Signing.
What You Actually Ship
After a build, Tauri produces one or more platform‑specific artifacts inside src-tauri/target/release/bundle/. The exact files depend on the operating system you build on and the bundle formats enabled in your configuration.
| Platform | Typical bundle formats | File extension(s) |
|---|---|---|
| Windows | NSIS installer, MSI, WiX | .exe (installer), .msi |
| macOS | DMG, app bundle | .dmg, .app (directory) |
| Linux | AppImage, Deb, RPM | .AppImage, .deb, .rpm |
Those files are what you hand to users. A .app bundle on macOS is self‑contained, but most users expect a .dmg. On Linux, AppImage is the easiest single‑file format because it bundles the required WebKitGTK libraries. A plain binary without a package is not feasible for Tauri on Linux — the app needs WebKitGTK at runtime, and packaging it into an AppImage or Flatpak keeps things portable.
Setting the Version Before You Distribute
Every distribution must carry a version number. Tauri resolves it from the version field in tauri.conf.json. If that field is absent, it falls back to the package.version in src-tauri/Cargo.toml.
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/schema.json",
"productName": "MyApp",
"version": "1.2.0",
"identifier": "com.example.myapp",
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
"devUrl": "http://localhost:1420",
"frontendDist": "../dist"
},
"app": {
"withGlobalTauri": true,
"windows": [],
"security": { "csp": null }
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/icon.ico",
"icons/icon.icns"
]
}
}
Version mismatch across config files:
If you set a version in tauri.conf.json, that version overrides the one in Cargo.toml — but your Rust backend and frontend can still read the Cargo.toml value independently. Always keep them aligned to avoid confusion during debugging. A quick manual check before a release prevents the classic “we tagged 2.0.0 but the app still says 1.0.0” scenario.
Sharing Installers
The most common distribution method is to provide an installer file that users download and run. The build command already produces those installers when the bundle configuration is active. You control which formats get built with the bundle section in tauri.conf.json, and you can even pass command‑line flags to override the config.
For Windows, the two main formats are the NSIS installer and MSI. NSIS is the default and produces a .exe installer with a familiar wizard. MSI is preferred in enterprise environments that use Group Policy deployment.
Run the build on a Windows machine:
npm run tauri build
The output lands in src-tauri/target/release/bundle/. You’ll find both an .exe and, if enabled, an .msi in the msi/ subfolder.
To generate only a specific format (e.g., MSI), you can split build and bundle:
npm run tauri build -- --no-bundle
npm run tauri bundle -- --bundles msi
Windows SmartScreen warnings:
An unsigned installer triggers a SmartScreen warning that says “Windows protected your PC.” Most users won’t click past it. You need an Authenticode certificate to sign the installer and remove that warning. See the code signing chapter for the full procedure.
Portable Applications
Sometimes an installer is overkill — you just want a self‑contained executable that users can copy onto a USB stick or drop into a folder.
- Windows: The NSIS bundler supports a portable mode. Set
bundle > nsis > installModeto"portable"intauri.conf.json. The build output is a single.exethat writes no registry entries and creates no Start Menu shortcuts. This works well for tools, utilities, and beta releases. - macOS: The
.appbundle is inherently portable. You can compress it into a zip and share it. Just remember that notarization is still required for users to open it without a security override. - Linux: The AppImage format is the closest equivalent. It bundles everything the app needs, including a compatible WebKitGTK. A truly standalone binary without any bundled libraries is not realistic for Tauri on Linux because the system WebKitGTK varies too much across distributions.
Portable mode and updates:
A portable app usually lacks an integrated auto‑update mechanism because it has no consistent install location. If you plan to ship a portable version, consider including a manual “check for updates” button that points to your website or GitHub Releases page.
Hosting Installers on Your Own Website
Once you have the signed installer files, you can put them behind a download button on your product site. The server just needs to serve the files with the correct MIME types. For common bundles:
.exe:application/vnd.microsoft.portable-executable.msi:application/x-ole-storage.dmg:application/x-apple-diskimage.AppImage:application/octet-stream.deb:application/vnd.debian.binary-package.rpm:application/x-rpm
Static hosting on services like Netlify, Vercel, or S3 with CloudFront works fine. Pay attention to two details that get missed surprisingly often:
- Set proper Cache-Control headers so that when you replace an installer with a new version, returning users don't get a stale cached file.
- Avoid renaming the file between releases if you plan to use auto‑updates later. The update plugin relies on a consistent filename or a manifest that maps versions to URLs.
If your app uses the Tauri updater plugin, you also need to upload a JSON manifest alongside the binaries. That manifest describes the available version, the download URL for each platform, and the signature that proves the update came from you. The updater chapter covers this in detail, but the distribution point is that the manifest must live at a public URL your app can reach.
Distributing via GitHub Releases
GitHub Releases is the most common distribution pipeline for open‑source Tauri apps. Each release can hold binaries for all three platforms, and you get a permanent URL that the updater plugin can query.
Creating a Release Manually
Build the application on each target platform
Run the build on Windows, macOS, and Linux machines — or use a CI runner. The artifacts land in src-tauri/target/release/bundle/.
Sign the binaries
On macOS: notarize the DMG and app bundle. On Windows: sign the installer with an Authenticode certificate. On Linux: no mandatory signature, but you should still sign your AppImage with the Tauri updater private key if you plan to ship updates.
Generate a signing key specifically for the updater:
npx @tauri-apps/cli signer generate -w ~/myapp-updater.key
This key pair is separate from the OS code‑signing certificate. The private key signs update bundles; the public key lives in tauri.conf.json.
Create a new GitHub Release
Go to your repository’s Releases page and click “Draft a new release”. Tag it with the version number (e.g., v1.2.0). Fill in the release notes.
Upload the installer files
Attach the built bundles: the Windows installer, the macOS DMG, the AppImage, etc. The release page handles serving them with the right Content‑Type.
Upload the update manifest (if using auto‑update)
Drop the JSON update file into the release assets as well. The updater plugin will fetch the release metadata from GitHub’s API and compare versions.
Automating the Pipeline with GitHub Actions
Manual steps work for one‑off releases, but they don’t scale. The tauri-action GitHub Action builds, signs, and publishes your app directly to a GitHub Release — triggered by pushing a version tag.
Below is a production‑ready workflow that builds for Windows (x86_64), macOS (both Apple Silicon and Intel), and Linux. It expects code‑signing secrets for macOS and Windows, plus the Tauri updater private key.
name: Release
on:
push:
tags:
- "v*"
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
jobs:
release:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- platform: ubuntu-22.04
args: ""
rust_target: x86_64-unknown-linux-gnu
- platform: macos-latest
args: "--target aarch64-apple-darwin"
rust_target: aarch64-apple-darwin
- platform: macos-latest
args: "--target x86_64-apple-darwin"
rust_target: x86_64-apple-darwin
- platform: windows-latest
args: ""
rust_target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: "npm"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust_target }}
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install frontend dependencies
run: npm ci
- name: Install Linux system deps
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev
- name: Import Apple signing certificate
if: runner.os == 'macOS'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
CERT_PATH=$RUNNER_TEMP/certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 24)
echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERT_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH"
- name: Build and release
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
with:
tagName: v__VERSION__
releaseName: "MyApp v__VERSION__"
releaseBody: "See [CHANGELOG](https://github.com/your-org/your-repo/blob/main/CHANGELOG.md) for details."
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
This workflow does several things that would be tedious and error‑prone to do by hand. It creates a temporary keychain on the macOS runner so that codesign can access the Apple signing certificate without a password prompt (the partition list step is what prevents a hanging GUI dialog). It caches Rust compilation so that subsequent runs complete in minutes instead of a quarter hour. It builds separate .dmg files for Intel and Apple Silicon Macs instead of a universal binary, which keeps downloads smaller and lets users grab only the one they need.
The build results appear as a draft GitHub Release, so you can review the assets and release notes before making it public. After publishing, anyone can download the installers from the repository’s Releases page.
Your pipeline is production‑ready when:
All four matrix jobs complete without errors and the draft release shows a full set of installer files. On the next push of a version tag (e.g., v1.3.0), the workflow runs again and the old concurrency group is cancelled if you push a fix quickly. This means you can tag, review, and publish entirely from GitHub without touching your local machine.
Common Release Pipeline Mistakes
- Using
npm installinstead ofnpm ciin CI.npm cirespects the lockfile exactly and fails if there are mismatches — that’s what you want for reproducible builds. - Forgetting to add WebKitGTK dependencies on the Linux runner. Without
libwebkit2gtk-4.1-dev, the Rust compilation fails with linker errors about missing libraries. The workflow above includes that step only for the Linux matrix entry. - Storing the Tauri updater private key in plain text inside the repository. That key is as sensitive as a code‑signing certificate. Always inject it as a GitHub secret and never echo it in logs.
Summary
This chapter set up the machinery that places your app in front of users. With signed installers, a release pipeline, and a download page, you have the foundation.