Docker
For self-hosters with Docker installed who want zero cloud dependencies. By the end you’ll have the dashboard running with IMAP email polling, everything stored locally in SQLite.
Docker Compose is the recommended self-host path. One container runs FastAPI plus the prebuilt React dashboard; a sidecar runs the IMAP poller. SQLite lives on a shared named volume. Start here in demo mode, then configure real Gmail IMAP and set up notifications.
What you get
Section titled “What you get”- FastAPI backend at
http://localhost:8000 - React dashboard served from the same container at
/ - IMAP poller sidecar that polls Gmail every 60 seconds once credentials are set — and idles quietly until then, so a bare
docker compose upnever crash-loops - SQLite at
data/finance.db(real mode) ordata/demo.db(demo mode, the default on first run), on a shared named volume
Run it
Section titled “Run it”If you already followed Quickstart, the stack is up — skip to “Where data lives” below. Otherwise:
-
Clone the repository.
Terminal window git clone https://github.com/tvhahn/tidings.gitcd tidings -
Start the stack.
Terminal window docker compose up -d -
Open the dashboard at http://localhost:8000.
Out of the box this runs in demo mode — a seeded SQLite database (data/demo.db) with sample transactions, no real accounts touched. To wire up your own data: copy .env.example to .env and add your Gmail IMAP credentials (email setup walks through it), set demo_mode: false in data/config.json, and restart. Your transactions land in data/finance.db; the seeded demo data stays in its own database and never mixes with yours.
Where data lives
Section titled “Where data lives”| Path | Contents |
|---|---|
data/config.json | Timezone, demo mode, storage backend, AI on/off, dashboard password hash |
data/finance.db | Real transactions, categories, overrides, budgets |
data/demo.db | Seeded sample data shown in demo mode |
Credentials — the Gmail App Password, OpenAI key, and notification provider tokens — live in .env as environment variables, not in data/. Every data/config.json key is documented in the configuration reference.
The data/ directory is gitignored and lives in a Docker named volume (tidings_finance_data, prefixed by your Compose project name). To inspect or back it up from outside the container:
docker run --rm \ -v tidings_finance_data:/data \ -v "$(pwd)/backup":/out \ alpine cp -r /data /outSubstitute your actual volume name from docker volume ls if your project directory isn’t tidings. To start over, docker compose down -v removes the volume along with the containers.
Updating
Section titled “Updating”git pulldocker compose pulldocker compose up -dTidings is pre-1.0; APIs and schemas may shift between minor versions. Read CHANGELOG.md before upgrading.
Health check
Section titled “Health check”The sidebar header shows a status dot — green, amber, or red — carrying the same state curl http://localhost:8000/api/v1/health returns:
ok(green) — the poller checked in within the last 5 minutes (or IMAP isn’t configured), and no parser looks stuck.degraded(amber) — the last poll was 5–30 minutes ago, or a bank email failed to parse in the last 7 days. A failed parse is the early warning for template drift. Checkdocker compose logs imap-poller.stale(red) — no poll in over 30 minutes, or the most recent transaction is more than 14 days old. The usual cause is a bank changing its email format so the parser silently errors; look in the logs forFailed to parse email.
Troubleshooting
Section titled “Troubleshooting”- Port 8000 already in use — change the host side of the mapping in
docker-compose.yml(for example"8001:8000") and openhttp://localhost:8001. exec format erroron Raspberry Pi / Apple Silicon — images are multi-arch (amd64 + arm64), so this is usually a stale cache.docker compose pull, or build locally.- IMAP poller can’t connect — Gmail needs an App Password and 2-Step Verification, not your account password. Email setup walks through it.
- Permission errors on
data/— the named volume is owned by the container user. After switching between a bind mount and the named volume, reset withdocker compose down -v(this deletes the volume’s data). - Transactions stopped appearing — the usual cause is a bank changing its email template. Look in
docker compose logs imap-pollerforFailed to parse email, then open a parser-broken issue with a redacted email body.
Looking for AWS instead?
Section titled “Looking for AWS instead?”The same parsers also run as an AWS Lambda triggered from S3. Storage moves to DynamoDB, notifications move to SNS. See AWS deployment.