Skip to content

Development & Setup

Follow this guide to set up the development environment, spin up the database, and run the Scala backend.


Ensure you have the following installed on your system:

  • mise-en-place (environment manager and task runner)
  • sbt (Scala build tool; brew install sbt)
  • A Docker-compatible container runtime for running PostgreSQL locally (e.g. Rancher Desktop or Colima)
  • GitHub CLI authenticated with your account — the backend depends on the lv.id.jc:dicechess-engine-scala artifact from GitHub Packages, which requires authentication even for public packages

Run the setup task to install pinned tools and register lefthook git hooks:

Terminal window
mise run setup

Spin up the local PostgreSQL database instance in the background using Docker Compose:

Terminal window
mise run db:up

This runs a PostgreSQL instance listening on port 5432 with:

  • Database: dicechess_analytics
  • Username: dicechess_user
  • Password: dicechess_password
Terminal window
mise run run

Database migrations are applied automatically: the backend runs Flyway on startup, so there is no separate migration step. Migration scripts live in src/main/resources/db/migration/.

Once started, the interactive API documentation (Swagger UI, generated from the Tapir endpoint definitions) is available at:


The backend depends on the game engine, lv.id.jc:dicechess-engine-scala, published to GitHub Packages Maven. Unlike Maven Central, GitHub Packages requires authentication even for public packages (a token with the read:packages scope) — so any sbt command that resolves dependencies needs a username and token.

Rather than repeat that credential dance in every task, the build resolves it once, in build.sbt:

GitHub Packages validates only the token — any non-empty username is accepted — so the build never needs a network call to discover the account name. (credentials is an sbt setting, evaluated on every load, so a network lookup here would slow down every command and break offline work.) The token is resolved as follows:

  1. In CI, the workflow exports GITHUB_TOKEN; the build uses it directly.
  2. Locally, when that variable is absent, the build reads it from the GitHub CLI via gh auth token, which returns the token from the OS keychain without touching the network — so it works offline and the token is never written to a file or a shell profile.

This is why the Scala build tasks are plain sbt ... with no credential prefix, and why a bare sbt invocation works too: just keep gh auth login current.


mise tasks are configured in mise.toml for easy execution:

CommandDescription
mise run setupInstalls pinned tools and registers lefthook git hooks.
mise run db:upLaunches only the PostgreSQL container.
mise run db:downStops and removes only the PostgreSQL container (data volume survives).
mise run stack:upStarts db + api + ui from published images.
mise run stack:downStops and removes all compose services.
mise run checkRepo-wide gate: scalafmt check plus coverage-gated tests on real PostgreSQL.
mise run formatRuns scalafmt across the Scala sources.
mise run compileCompiles the backend.
mise run testRuns the test suite without the coverage/clean overhead.
mise run runStarts the API server on port 8000.
mise run docs:devStarts the Astro/Starlight dev server at http://localhost:4321.
mise run docs:buildCompiles the documentation site into static HTML inside docs/dist/.

Environment variables (compatible with docker-compose):

VariableDefaultNotes
DATABASE_URLpostgres://, postgresql://, or postgresql+asyncpg:// accepted (the +asyncpg form is kept only for legacy .env compatibility — it is rewritten to a JDBC URL)
POSTGRES_HOST/PORT/DB/USER/PASSWORDdocker-compose defaultsused when DATABASE_URL is absent
HTTP_HOST / HTTP_PORT0.0.0.0 / 8000
CORS_ORIGINShttp://localhost:5173,http://localhost:3000comma-separated

The test suite uses testcontainers against a real PostgreSQL. On Rancher Desktop two machine-local accommodations are needed:

  1. ~/.testcontainers.properties:

    docker.host=unix\:///Users/<you>/.rd/docker.sock
  2. mise.local.toml at the repo root (gitignored), so every task gets the variable regardless of the shell session — the ryuk cleanup sidecar cannot start against the Rancher moby VM, so cleanup falls back to JVM shutdown hooks. CI on ubuntu-latest keeps ryuk enabled.

    [env]
    TESTCONTAINERS_RYUK_DISABLED = "true"

The Docker API version is pinned to 1.43 via Test / javaOptions in build.sbt: docker-java does not negotiate and its default (1.32) is rejected by Docker 29+ daemons.


mise run setup registers two lefthook tiers:

  • pre-commit: betterleaks secret scan + native scalafmt check on staged files (milliseconds per commit).
  • pre-push: a hermetic full-module scalafmt check (~1s). Tests deliberately stay in CI.

Run all pre-commit jobs manually across the whole codebase with mise run hook:run.


The historical archive (140k+ games from the frozen dicechess-lab SQLite database) has already been imported into the production PostgreSQL instance — the one-time Python ETL that performed it was retired together with the rest of the Python codebase.

Ongoing ingestion uses the transactional POST /api/games endpoint, which validates every game against dicechess-engine-scala before persisting it. See the Game Ingestion page for the contract.