Savvy Docs

Installation

Install Savvy with Docker Compose — persistent volume, environment variables and health-check endpoints for a production-ready self-hosted expense tracker.

Docker Compose is the recommended way to run Savvy for anything beyond a quick look. It gives you a named volume for your data, a public URL and a health check, all in one file you can version-control.

Docker Compose

Create the compose file

Save this as docker-compose.yml. The image: tag is pinned to the current release so your deploy is reproducible:

docker-compose.yml
services:  savvy:    image: truenormis/savvy:v1.2.3    container_name: savvy    restart: unless-stopped    ports:      - "3000:80"    volumes:      - savvy-data:/data    environment:      - APP_URL=https://savvy.yourdomain.com      - TZ=Europe/Kyiv    healthcheck:      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/livez"]      interval: 10s      timeout: 3s      start_period: 60s      retries: 3volumes:  savvy-data:

Start it

docker compose up -d

Migrations run automatically on first start. Once the container is healthy, open your APP_URL (or localhost:3000 locally) and create your account.

Environment variables

VariableDescriptionDefault
APP_URLPublic URL of your instancehttp://localhost
TZTimezoneUTC

Set APP_URL correctly

APP_URL must match the URL users actually reach the app on (including https:// when behind a proxy). It drives absolute links, redirects and cookie scoping — a mismatch breaks logins and 2FA. See Reverse proxy.

Data & persistence

Everything lives in the /data volume: the SQLite database (database.sqlite) and uploads. As long as that volume survives, your data survives container restarts, upgrades and re-creates. Never bind-mount /data onto a network filesystem — SQLite needs a real local disk for its locking to work.

Health checks

Two probe endpoints are exposed for orchestrators and uptime monitoring (responses use the IETF application/health+json format):

EndpointPurposeHealthyUnhealthy
/livezLiveness — the app process is up. Use it for restart decisions.200
/readyzReadiness — database reachable and migrations applied. Gate traffic.200503

/livez stays up during maintenance mode; /readyz returns 503 so traffic drains while the instance is not ready.

How it works

One container runs everything under Supervisor — Nginx, PHP-FPM, the scheduler (recurring transactions, automatic exchange-rate updates) and a queue worker for background jobs. SQLite lives in /data; no external database, cache or queue service is required.

On this page