Run it locally
Toolchain, one command, and the ports everything lands on.
From a fresh clone
Toolchain
Java 26, Gradle and Bun are pinned in .mise.toml. mise installs those exact versions and changes
nothing else on the machine.
curl https://mise.run | sh # once, ever
mise install # in the repo rootmise does not activate in non-interactive shells. A script that calls ./gradlew without
JAVA_HOME set will look like the build is broken when the toolchain is simply not on the path.
tools/dev/run.sh launches every backend through mise exec for this reason.
Environment
The .env.example templates are committed; the .env files are not. Copy each one and fill it in.
There is no root .env, ever — env lives in tools/dev/env/*.env for backends and
apps/*/web/.env for frontends, each with a committed example beside it
(ADR-0018).
Bring it up
bun run all # Postgres, both backends, both frontends
bun run finloop # or just the Finloop half
bun run iam # or just IAMtools/dev/run.sh starts the database and waits for it; mprocs multiplexes the rest into one board,
a pane per process.
Port map
| Service | Port | Database |
|---|---|---|
| PostgreSQL 17 | 5442 | — |
| IAM backend | 9000 | lifeos_iam |
| IAM web | 3000 | — |
| Finloop backend | 8080 | lifeos_finloop |
| Finloop web | 3001 | — |
| Handbook | 3002 | — |
Why 5442 and not 5432? This machine already runs a shared PostgreSQL that another estate depends on. lifeos gets its own server so the two can never share a volume or fight over a version bump.
The gates
These are the same commands CI runs. Assert nothing green without their real output.
./gradlew build --rerun-tasks # compile, ArchUnit, Testcontainers-backed tests
bun run check # Biome: format, lint, import order
bunx tsc --noEmit # types
bunx vite build # each web app
bunx vitest run # frontend unit tests./gradlew build reports BUILD SUCCESSFUL with every task UP-TO-DATE. That is a cache hit, not
a verification. Use --rerun-tasks when the answer matters.