A self-hostable, offline-first holiday budget tracking app. Track trip expenses in multiple currencies, view real-time spending analytics, and never lose data offline. Part of the Bretzel app universe.
# docker-compose.yml
services:
42:
image: ghcr.io/bretzel-app/42:latest
ports:
- "3000:3000"
volumes:
- 42-data:/data
environment:
- ORIGIN=https://trips.example.com
restart: unless-stopped
volumes:
42-data:docker compose up -dOpen http://localhost:3000 and create your admin account on first visit.
Add SMTP settings to enable email notifications (welcome emails, trip invitations, security alerts):
environment:
- SMTP_HOST=smtp.example.com
- SMTP_PORT=587
- SMTP_USER=your-username
- SMTP_PASS=your-password
- SMTP_FROM=42 <[email protected]>Auto-enabled when SMTP_HOST is set. All sends are best-effort and never block API responses.
git clone <repo-url> 42 && cd 42
pnpm install
pnpm build
DATABASE_URL=./data/42.db ORIGIN=http://localhost:3000 node buildpnpm install
pnpm devRun make help to see all available commands, or use pnpm directly:
pnpm test # Unit + E2E tests
pnpm check # Type checking
pnpm build # Production build- Create trips with budgets, dates, destinations, and group size
- Log expenses in any currency with manual exchange rates
- Live dashboard — budget gauge, daily spend chart, category breakdown, projections
- Six built-in categories: Food, Accommodation, Transport, Activities, Shopping, Misc
- Multi-currency support with per-trip exchange rate management
- PWA — installable, works offline via IndexedDB + LWW CRDT sync
- Email notifications for account creation and trip invitations (optional SMTP)
- Multi-user auth (Argon2) with admin/user roles
- Docker deployment with a single command
| Variable | Default | Description |
|---|---|---|
NODE_ENV |
development |
Set to production for deployments |
DATA_DIR |
./data |
Directory for SQLite database and files |
DATABASE_URL |
./data/42.db |
Path to the SQLite database |
ORIGIN |
http://localhost:3000 |
Public URL of the app |
SMTP_HOST |
(empty) | SMTP server hostname — setting this enables email notifications |
SMTP_PORT |
587 |
SMTP port (587 for STARTTLS, 465 for SSL) |
SMTP_USER |
(empty) | SMTP username (optional for unauthenticated relays) |
SMTP_PASS |
(empty) | SMTP password |
SMTP_FROM |
42 <noreply@localhost> |
Sender address for outgoing emails |
| Layer | Technology |
|---|---|
| Framework | SvelteKit 2 (Svelte 5 runes) |
| Language | TypeScript (strict) |
| UI | Tailwind CSS 4 |
| Database | SQLite (better-sqlite3) + Drizzle ORM |
| Client DB | IndexedDB (idb) |
| Sync | LWW CRDTs |
| Auth | Argon2 + session cookies |
| Charts | Pure SVG (no libraries) |
| Testing | Vitest + Playwright |
| Container | Docker (multi-stage) |
| CI/CD | GitHub Actions |
- CI — lint, type check, unit tests, E2E tests, Docker build on every push/PR
- Release — builds and pushes Docker image on
v*tags
Configure registry via GitHub Secrets: REGISTRY_URL, REGISTRY_USER, REGISTRY_TOKEN.
