If you want to run privacytracker from a source checkout (for development or platforms we don’t ship binaries for), see Build from source under the Develop tab.
- Desktop app (macOS)
- Homebrew (macOS)
- Docker (Linux / macOS)
Signed and notarized for both Apple Silicon (Auto-updates use Tauri’s updater with an ed25519 signature check on every patch.
aarch64) and Intel (x86_64). Releases after v0.1.2, starting with v0.3.0, need macOS 13.5 or later; v0.1.2 runs on macOS 11 and later.1
Download the latest .dmg
2
Open and drag to /Applications
Gatekeeper opens it without the “unidentified developer” warning because the build is signed with a Developer ID certificate and notarized by Apple.
3
Launch privacytracker
The SQLite database is created at
~/Library/Application Support/privacytracker/ and survives app updates.The desktop app is moving off its bundled Node process onto a Rust backend built into the app. The first release on it hasn’t been published as of September 2026; v0.1.2, the latest, runs on Node. From that release on:
- The app serves itself, with no separate Node process, and nothing is unpacked into the data folder: the frontend ships read-only inside the signed app. Earlier releases extract a ~200 MB
standalone/folder there; once you’ve moved to the Rust backend nothing uses it, and you can delete it. - The data folder stays where it is, and either build opens the database the other left behind, so the switch never stands in the way of going back to a release on Node.
- The app reuses the local port it had last time, so what a page keeps in the browser, such as the accessibility quick toggles, survives a relaunch unless something else has taken the port. Earlier releases lose it at every launch.
- The licence notices ship inside the app, in
Contents/Resources/third-party/:NOTICE,LICENSE,V8-LICENSEandTHIRD-PARTY-RUST.md, which lists every Rust crate compiled in. The app’s Legal page names the crates chosen directly and links the full list. - macOS 13.5 stays the minimum.
Windows and Linux desktop builds aren’t published yet. For those platforms, use Docker, or build from source on the platform of your choice.
Colima
On macOS without Docker Desktop, Colima gives you a lightweight Linux VM:colima stop when you’re done; the privacytracker-data volume persists.
iPhone import helper
Companion Python tool (stdlib-only, Python 3.9+) that exports installed-app lists from local Finder/iTunes backups or a connected iPhone viaideviceinstaller. It’s not wired into the running app — it produces a .txt or .csv you feed back into the web onboarding flow.
Behind a reverse proxy
For trusted-LAN deployments the project ships working Compose stacks in the repo checkout — you don’t need to write a proxy config from scratch:.env.example next to the compose file, fill it in, and bring the stack up from that directory.
Three rules apply here, and the samples only cover the first. privacytracker enforces a same-origin CSRF check on every destructive route, so the proxy must forward the original Host header — both sample proxies do that out of the box.
The second one is yours to add. privacytracker only honours X-Forwarded-For / X-Forwarded-Host when PRIVACYTRACKER_TRUST_PROXY is set, and neither .env.example carries it, nor do the sample compose.yaml files pass it into the app container. Until you add it to the privacytracker service’s environment: block yourself — the root docker-compose.yml documents the variable inline — rate-limit keys and audit_log.actor_ip collapse to the literal local, meaning one shared rate-limit bucket per route and no usable client IP in the audit trail. See Hardening → TLS via reverse proxy for the threat-model walkthrough.
The third one is what actually breaks the deployment, and it is also yours to add. proxy.ts rejects every request whose Host isn’t allowlisted — GET included, before any other gate — with 400 {"error":"Host not allowed"}, and the default allowlist is loopback only. Neither sample passes PRIVACYTRACKER_ALLOWED_HOSTS into the app container and neither .env.example mentions it; the stacks work out of the box only because PRIVACYTRACKER_HOST defaults to privacytracker.localhost and *.localhost is treated as loopback. The moment you follow the .env.example comment and set a LAN DNS name, mDNS name, or a real domain, every request 400s until you add that same name to PRIVACYTRACKER_ALLOWED_HOSTS in the privacytracker service’s environment: block.
Verify the install
{"status":"not_ready"} (HTTP 503), check write permissions on the data/ directory and confirm the process can open data/privacy.db. WAL mode plus a 5-second busy_timeout are set on every open, so concurrent reads while a write is in flight should never block longer than that.
Where the data lives
The database is a single SQLite file. Back it up by copying the file (with the app stopped, or via the in-app Settings → Backup export which produces a versioned JSON snapshot you can restore later).