Skip to content

Quickstart

You can have a working Scani instance running locally in five minutes. The default stack is completely self-contained — no external accounts, no provider API keys.

  • Bun ≥ 1.3
  • Docker (Docker Desktop, OrbStack, or any compatible runtime)
  • A free port at: 5173 (SPA), 3011 (api), 8082 (data-provider), 5433 (Postgres), 6380 (Redis), 9000/9001 (MinIO), 1026/8026 (Mailpit).
  1. Clone and seed the env file.

    Terminal window
    git clone git@github.com:MGrin/scani-oss.git
    cd scani-oss
    cp .env.example .env
  2. Install workspace dependencies.

    Terminal window
    bun install
  3. Boot the full stack.

    Terminal window
    bun run dev:stack

    This runs scripts/sync-env.ts (materialises per-app .env files from the root .env) then docker compose --profile full up -d --build. First boot pulls images and builds the app containers — give it a few minutes.

  4. Open the SPA.

    Terminal window
    open http://localhost:5173

    Sign in with a magic-link email. Local emails land in Mailpit at http://localhost:8026 — click the link there.

  5. Stop the stack when you’re done.

    Terminal window
    bun run dev:stack:down

    Containers go away; named volumes (your Postgres data, your MinIO bucket) persist until you docker compose down -v.

ServiceURL
Frontend SPAhttp://localhost:5173
API (tRPC + Elysia)http://localhost:3011
Data-provider (tRPC)http://localhost:8082
Postgreslocalhost:5433 (scani / scani)
Redislocalhost:6380
Mailpit SMTPlocalhost:1026
Mailpit UIhttp://localhost:8026
MinIO S3 APIhttp://localhost:9000
MinIO consolehttp://localhost:9001 (minioadmin / minioadmin)
  • Magic-link sign-in (emails land in Mailpit).
  • Manual holdings with manual prices.
  • FX conversion (the Frankfurter provider needs no key).
  • Screenshot uploads (stored in the local MinIO bucket).
  • The full SPA, dashboard, vaults, groups.

These integrations are gated by an API key — without the key, the corresponding tRPC routes return PRECONDITION_FAILED and the rest of the app keeps working.

ProviderEnv varUnlocks
CoinGeckoCOINGECKO_API_KEYCrypto prices on the paid tier (public tier works without a key but is rate-limited)
FinnhubFINNHUB_API_KEYPublic-equity prices
EtherscanETHERSCAN_API_KEYEVM wallet balances (one key covers every EVM chain)
HeliusHELIUS_API_KEYSolana balances + transactions
OpenAIOPENAI_API_KEYScreenshot parsing
Binance OAuthBINANCE_OAUTH_CLIENT_ID / _SECRET / _REDIRECT_URIBinance exchange connection flow

See Optional integration keys for the full list.

Running services on the host instead of in containers

Section titled “Running services on the host instead of in containers”

If you prefer to iterate on api / worker / frontend on the host (faster HMR, easier debugger attach) and only run infra in Docker:

Terminal window
docker compose up -d postgres redis mailpit minio
bun install
bun dev # api + frontend/app concurrently
bun dev:worker # separate terminal — the BullMQ consumer
bun dev:data-provider # separate terminal — outbound 3rd-party calls
  • compose up fails with a name conflict. One-shot init containers (env-sync, deps, minio-init, migrate) exit cleanly but linger with their container names reserved. bun run dev:stack:down removes them. compose up without a prior down will hit the conflict.
  • API keys go in .env, not in compose. .env is the single source of truth. scripts/sync-env.ts propagates it to per-app .env files. Editing docker-compose.yml directly will drift.
  • ENCRYPTION_KEY must match between api and worker. They’re the same value in .env.example for a reason — if they differ, the worker cannot decrypt the integration credentials the api stored, and every import silently fails.
  • Persistent data lives in named volumes. docker compose down preserves them; docker compose down -v wipes them. The Postgres data, the MinIO bucket, and the Redis AOF all live there.
  • Two scani checkouts running in parallel share nothing by default. Compose volumes are scoped per project name (the parent directory), so each worktree gets its own Postgres / Redis / MinIO data. If you want them to share, use --project-name explicitly. If you don’t, remember each bun run dev:stack:down -v only wipes that worktree’s data.

The dev stack above is the foundation for the production deployment flow at Self-hosting → Production. Two notable differences:

  • Migrations are an explicit step. Prod compose ships them as a profile-gated one-shot (docker compose --profile migrate run --rm migrate) — you run it on first install and on every upgrade. The dev compose runs them automatically as part of bun run dev:stack.
  • /api/readyz validates schema. The prod compose api healthcheck fails (and frontend-app is blocked) until migrations have been applied. Forgetting to migrate is a loud failure, not a silent one.