Open-source, self-hosted crash reporting for Kotlin Multiplatform apps.
CrashWatch captures unhandled exceptions and non-fatal errors from Android, iOS, Desktop, and Web targets, stores them in PostgreSQL, and surfaces them in a Compose Multiplatform dashboard — all running on your own infrastructure.
Hosted version: Don't want to self-host? crashwatch.dev offers a fully managed SaaS with a free trial.
- Kotlin Multiplatform SDK — one integration for Android, iOS, Desktop (JVM), and Web (JS/Wasm)
- Crash + non-fatal recording — stack traces, breadcrumbs, custom metadata, app version
- Compose Multiplatform dashboard — native desktop app, Android/iOS, or web — same codebase
- JWT auth — role-based access (Admin / Viewer)
- Multi-app support — manage multiple apps from one server
- Self-hostable — single Docker image, PostgreSQL backend, one
docker composecommand - Open source — MIT licensed
- Docker + Docker Compose
- A domain or server reachable by your apps (or
localhostfor local testing)
curl -O https://raw.githubusercontent.com/sobuumedia/crashwatch/main/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/sobuumedia/crashwatch/main/.env.example
cp .env.example .envGenerate a JWT secret with openssl rand -hex 32 and paste the result — a dotenv
file does not expand $(...).
DB_PASS=a_strong_database_password
JWT_SECRET=paste_output_of_openssl_rand_hex_32
ADMIN_USERNAME=admin
ADMIN_PASSWORD=a_strong_admin_password
# Your public domain (dashboard + API are served same-origin via the proxy):
SITE_ADDRESS=crashwatch.example.com
ALLOWED_ORIGIN=https://crashwatch.example.com
ACME_EMAIL=admin@example.com
JWT_EXPIRY_HOURS=24
DEV_MODE=falsedocker compose -f docker-compose.prod.yml up -dThis starts PostgreSQL, the API server, the web dashboard, and a Caddy reverse proxy that terminates TLS and serves everything on one origin.
# Health check (through the proxy):
curl https://crashwatch.example.com/health # → {"status":"ok"}
# Log in:
curl https://crashwatch.example.com/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<ADMIN_PASSWORD>"}'
# → {"token":"...","role":"ADMIN"}Open https://crashwatch.example.com in a browser to reach the dashboard.
Local testing:
docker compose up --build(the non-prod file) serves the same stack over plain HTTP athttp://localhost(dashboard) with the API also exposed directly onhttp://localhost:8080.
| Variable | Required | Default | Description |
|---|---|---|---|
DB_PASS |
Yes | — | PostgreSQL password for the crashwatch user |
JWT_SECRET |
Yes | — | Secret for signing JWTs — use openssl rand -hex 32 |
ADMIN_USERNAME |
Yes | — | Username for the initial admin account |
ADMIN_PASSWORD |
Yes | — | Password for the initial admin account |
ALLOWED_ORIGIN |
Yes | — | CORS origin allowed to access the API. Set it to the same public origin as the dashboard, e.g. https://crashwatch.example.com |
SITE_ADDRESS |
Prod | — | Public domain for the Caddy proxy; Caddy fetches a Let's Encrypt cert for it |
ACME_EMAIL |
Prod | — | Email for Let's Encrypt registration |
JWT_EXPIRY_HOURS |
No | 24 |
How long issued tokens remain valid |
DEV_MODE |
No | false |
Set true to allow any CORS origin (dev only) |
DB_URL |
No | set in compose | JDBC URL (overridden in docker-compose) |
DB_DRIVER |
No | set in compose | JDBC driver class |
DB_USER |
No | set in compose | Database username |
- JDK 21
- Android Studio or IntelliJ IDEA (for the dashboard)
# Start a throwaway local Postgres (Flyway 11 no longer supports SQLite):
docker run --rm -d --name cw-dev-db -p 5432:5432 \
-e POSTGRES_DB=crashwatch -e POSTGRES_USER=crashwatch \
-e POSTGRES_PASSWORD=crashwatch postgres:16
./gradlew :server:installDist # build runnable distribution → server/build/install/server
./gradlew :server:run # run locally on http://localhost:9000 (admin/admin)
./gradlew :server:test # run server tests (embedded SQLite, no external database)
:server:runserves on port 9000; in Docker/production the server bindsPORT(default 8080). For a full local stack identical to production, usedocker compose up --buildinstead.
./gradlew :composeApp:run # Desktop
./gradlew :composeApp:assembleDebug # Android APK
./gradlew :composeApp:wasmJsBrowserDevelopmentRun # Web (Wasm)
./gradlew :composeApp:jsBrowserDevelopmentRun # Web (JS)Open iosApp/iosApp.xcodeproj in Xcode to build for iOS.
Builds and runs the whole stack (Postgres + API + web dashboard + proxy) over plain HTTP:
cp .env.example .env # fill in values
docker compose up --build
# dashboard → http://localhost · API → http://localhost:8080Full setup guides for all targets are in crashreporter/SDK_SETUP.md:
The dashboard ships three ways, all from the same Compose Multiplatform codebase:
- Web — served automatically by the Docker stack (via the Caddy proxy) at your
SITE_ADDRESS. No separate install. - Desktop — download the latest release from the Releases page, or
./gradlew :composeApp:run. - Android / iOS — build from source (see above).
On first launch of the desktop/mobile app, enter your server URL and admin credentials to log in. (The web dashboard is same-origin, so it just needs your admin credentials.)
CrashWatch has two interchangeable, API-compatible backends:
- Ktor (
server/) — the default. Runs on the JVM with PostgreSQL; this is what the Docker images anddocker-compose*.ymluse. - PHP (
php-backend/) — a drop-in alternative for cheap shared hosting (PHP + MySQL, no JVM/Docker required). Seephp-backend/README.md.
Both expose the same HTTP API, so the SDK and dashboard work against either.
See CONTRIBUTING.md for how to report bugs, suggest features, and submit pull requests.
MIT — see LICENSE.