Selfhosted expense tracker with full multi-currency support. One container — done.
docker run -d -p 3000:80 -v savvy-data:/data truenormis/savvy:latestOpen localhost:3000 and create your account.
- Multi-currency — any fiat or crypto, transfers between them
- Auto exchange rates — currency rates updated automatically via API
- Recurring transactions — scheduled payments (daily, weekly, monthly, yearly)
- Automation rules — auto-categorize transactions based on conditions
- Debts — track loans and borrowings with payment history
- Budgets — set limits and track progress
- Categories & tags — flexible organization
- Multi-user — share with family or team, role-based access (admin/user)
- Rich analytics — Sankey diagrams, heatmaps, net worth tracking, expense pace
- CSV import — import transactions from bank exports with duplicate detection
- Backups — create, restore and download database backups
- 2FA — two-factor authentication via TOTP (Google Authenticator, etc.)
Fully responsive design built with ShadCN/UI — track expenses from your phone right after purchase.
version: "3.8"
services:
savvy:
image: truenormis/savvy:latest
container_name: savvy
restart: unless-stopped
ports:
- "3000:80"
volumes:
- savvy-data:/data
environment:
- APP_URL=https://savvy.yourdomain.com
- TZ=Europe/Kyiv
volumes:
savvy-data:| Variable | Description | Default |
|---|---|---|
APP_URL |
Public URL of your instance | http://localhost |
TZ |
Timezone | UTC |
version: "3.8"
services:
savvy:
image: truenormis/savvy:latest
container_name: savvy
restart: unless-stopped
volumes:
- savvy-data:/data
environment:
- APP_URL=https://savvy.yourdomain.com
- TZ=Europe/Kyiv
labels:
- "traefik.enable=true"
- "traefik.http.routers.savvy.rule=Host(`savvy.yourdomain.com`)"
- "traefik.http.routers.savvy.entrypoints=websecure"
- "traefik.http.routers.savvy.tls.certresolver=letsencrypt"
- "traefik.http.services.savvy.loadbalancer.server.port=80"
networks:
- traefik
volumes:
savvy-data:
networks:
traefik:
external: true- Run Savvy on internal port:
version: "3.8"
services:
savvy:
image: truenormis/savvy:latest
container_name: savvy
restart: unless-stopped
expose:
- "80"
volumes:
- savvy-data:/data
environment:
- APP_URL=https://savvy.yourdomain.com
networks:
- npm-network
volumes:
savvy-data:
networks:
npm-network:
external: true- In Nginx Proxy Manager, create proxy host pointing to
savvy:80
Savvy works out of the box on Kubernetes. Deploy as a single-pod Deployment with a PersistentVolumeClaim mounted at /data. Helm chart coming soon.
docker compose pull
docker compose up -dYour data is safe in the /data volume.
Backups can be managed directly from the UI (Settings → Backups).
Manual backup:
docker cp savvy:/data/database.sqlite ./backup-$(date +%Y%m%d).sqliteRestore:
docker cp ./backup.sqlite savvy:/data/database.sqlite
docker restart savvyYour data stays with you. SQLite database stored in /data volume — no external services required.
Laravel • SQLite • Docker • ShadCN/UI • Tailwind CSS
Contributions are welcome! Please open an issue first to discuss what you would like to change.
Made with ❤️ for people who want control over their finances


