QoreDB LogoQoreDB
Back to blog
ArchitectureTechnical journal

Auto-update in a Tauri app: mechanism and constraints

QoreDB is a desktop application, distributed as a binary installed on the developer's machine. Without an auto-update mechanism, each release requires the user to manually return to the download page, fetch the new installer, then run the procedure again. For a tool used daily,…

Raphaël – Creator of QoreDBRaphaël – Creator of QoreDB
5 min readUpdated Jul 18, 2026
Auto-update in a Tauri app: mechanism and constraints

QoreDB is a desktop application, distributed as a binary installed on the developer's machine. Without an auto-update mechanism, each release requires the user to manually return to the download page, fetch the new installer, then run the procedure again. For a tool used daily, this friction cuts off the flow of fixes and new features.

Tauri 2 offers a dedicated plugin, tauri-plugin-updater, that solves the problem with a simple architecture: a static manifest, signed binaries, and full client-side control over the timing and the UI of the update. Here is how QoreDB uses it.

The latest.json manifest on GitHub Releases

The updater plugin relies on a JSON manifest published at a stable URL. QoreDB's configuration points to https://github.com/QoreDB/QoreDB/releases/latest/download/latest.json. This file is attached to each GitHub release by the CI at build time, and the /releases/latest/download/ path always redirects to the latest published release. The manifest contains the version, the publication date, the notes, and for each platform the bundle URL plus its minisign signature.

On the infrastructure cost side: zero. No distribution server to maintain, no CDN to provision, no version database. GitHub Releases acts as the back-end, free and already integrated into the release workflow. For an open source desktop app, it is the most direct option.

Minisign signature and chain of trust

The minisign public key is embedded directly in tauri.conf.json. When installing an update, the plugin verifies the signature of the downloaded bundle with this key before running it. The corresponding private key is stored as a GitHub Actions secret under the name TAURI_SIGNING_PRIVATE_KEY, and every artifact produced by the release.yml workflow is signed automatically.

The benefit is concrete. An attacker who compromised the GitHub CDN or interposed a mirror could not get a malicious binary installed. Without a valid signature, the plugin refuses the installation and returns an error. This property matters especially for a database client, where the binary has access to the vault credentials and the active connections.

A custom UI rather than an imposed dialog

QoreDB's configuration deliberately disables the default dialog with "dialog": false. Rather than a native popup imposed by the plugin, the app shows a small button in the titlebar when an update is available, and a panel in Settings > General to trigger the check or the installation at any time.

The updateStore.ts store models the states of an update with four statuses: idle, available, installing, installed. The state is shared between the titlebar and the Settings page through an in-house store that uses useSyncExternalStore, with no dependency on an external framework.

Three reasons for this choice. First, visual consistency: the titlebar and the Settings use the app's Tailwind design system, not a system popup that would clash with the rest of the UI. Second, user control: it is the user who decides when to install, without abruptly interrupting a running query or a debug session. Finally, observability: the state is exposed to the whole UI, so we can show a badge during installation, a localized error message, and a restart button once the download is complete.

A check at startup, a clean opt-out

When the app starts in production mode, SessionProvider.tsx launches a check after a 4-second delay. The code boils down to an await check() call, followed by a setUpdateAvailable(update) if a new version is found. The delay gives the app time to finish booting and to reconnect the restored sessions before touching the network.

The user can disable this automatic check entirely through the checkUpdates preference in the startup pref store, accessible from Settings > General. This matters for air-gapped environments, tightly controlled machines, or simply developers who prefer to manage their version manually.

The installation flow on the UI side

When the user clicks the install button, the flow follows four short steps. setUpdateInstalling() sets the state to installing. update.downloadAndInstall() downloads the signed bundle, verifies the minisign signature, then launches the platform's native installer. setUpdateInstalled() is called once the download and installation are complete. The UI then shows a restart button that calls relaunch() from the process plugin to start again on the new version.

All errors are caught and stored in the store with a readable message. The titlebar button then switches to an error state, and the user can retry manually from Settings without having to restart.

A single stable channel, releases tested upstream

The latest.json manifest always points to the latest stable release. There is only one distribution channel, no separate beta or nightly. For a desktop app with a controlled release cycle, this scope is intentional: the effort goes into testing before publication, not into managing multiple channels. If a beta channel becomes necessary one day, it will be enough to host a second manifest at a distinct URL and expose the choice in Settings.

Automatic rollback is not handled either. If a release breaks a critical use for a user, they manually go back down to an earlier version from GitHub's Releases page. This is consistent with the local-first model: the user keeps control of the version they run, without a remote decision being able to push a downgrade onto their machine.

The result in practice

For the end user, updating QoreDB takes two clicks: one on the titlebar button when it appears, then one on Restart when the installation is complete. No interruption until they decide to launch the operation. For the maintainer, publishing a new version amounts to pushing a git tag; the CI builds, signs, and automatically attaches the manifest and the bundles to the release. The full cycle from release to distribution requires no manual action in between.

The final mechanism is simple, verifiable end to end, and depends only on the repo, its CI, and the minisign key. For a local-first desktop app, this approach avoids both the complexity of a dedicated distribution platform and the inertia of manual updates, without giving up the chain of trust between the published code and the binary that runs on the developer's machine.

Newsletter

Stay updated on new releases

Subscribe to get product releases, new drivers notifications, and technical tutorials.

🎁 Bonus: Get our free SQL Performance Cheat Sheet — 9 pages, PostgreSQL / MySQL / SQLite (PDF)!
Share
Auto-update in a Tauri app: mechanism and constraints - Blog - QoreDB