Skip to content

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.

  • 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 up never crash-loops
  • SQLite at data/finance.db (real mode) or data/demo.db (demo mode, the default on first run), on a shared named volume

If you already followed Quickstart, the stack is up — skip to “Where data lives” below. Otherwise:

  1. Clone the repository.

    Terminal window
    git clone https://github.com/tvhahn/tidings.git
    cd tidings
  2. Start the stack.

    Terminal window
    docker compose up -d
  3. 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.

PathContents
data/config.jsonTimezone, demo mode, storage backend, AI on/off, dashboard password hash
data/finance.dbReal transactions, categories, overrides, budgets
data/demo.dbSeeded 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:

Terminal window
docker run --rm \
-v tidings_finance_data:/data \
-v "$(pwd)/backup":/out \
alpine cp -r /data /out

Substitute 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.

Terminal window
git pull
docker compose pull
docker compose up -d

Tidings is pre-1.0; APIs and schemas may shift between minor versions. Read CHANGELOG.md before upgrading.

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. Check docker 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 for Failed to parse email.
  • Port 8000 already in use — change the host side of the mapping in docker-compose.yml (for example "8001:8000") and open http://localhost:8001.
  • exec format error on 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 with docker 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-poller for Failed to parse email, then open a parser-broken issue with a redacted email body.

The same parsers also run as an AWS Lambda triggered from S3. Storage moves to DynamoDB, notifications move to SNS. See AWS deployment.