Skip to content

Getting started

An AI-first Scala 3 backend template. Wine auctions are the showcase domain.

AI-first means the repo is wired for assistant-driven development out of the box: a committed .mcp.json gives your assistant a live compile/test loop (Metals) and a read-only upstream reference (the madrileno MCP server), and the docs/ double as its knowledge base. See docs/ai-assisted-dev.md for the full setup.

If you’re using this as a template, run the rename script first — it swaps madrileno for your project name + package and removes the auction demo:

Terminal window
./scripts/init-project.scala <project-name>

See docs/scripts.md for what it does and what else lives under scripts/.

You’ll need four tools. Install each from its package manager or official installer:

The three JVM tools also install together via SDKMAN:

Terminal window
sdk install java 21-tem
sdk install sbt
sdk install scalacli
Terminal window
docker compose up -d

That brings up four services on non-standard host ports so they don’t clash with anything you already have running:

Service Image Host port(s) Login
Postgres postgres:latest 55432 → 5432 postgres / postgres
Mailpit axllent/mailpit:latest 51025 (SMTP), 58025 (UI)
OpenObserve public.ecr.aws/zinclabs/openobserve:latest 55080 (UI + OTLP HTTP) root@example.com / Complexpass#123
Silo pgsty/silo:RELEASE.2026-09-16T00-00-00Z 59000 (S3 API), 59001 (Console UI) minioadmin / minioadmin

State persists across restarts in named volumes. To wipe and start clean:

Terminal window
docker compose down -v
docker compose up -d
Terminal window
cp .env.sample .env

The sample is wired against the docker-compose ports above — including a working Authorization header for OpenObserve so OTLP traces show up in the UI immediately.

JWT_SECRET is fine as-is for local dev — change it when you don’t want strangers to be able to forge tokens. External auth providers (FIREBASE_PROJECT_ID, OIDC_*) ship empty; the app boots fine without them and the dev login (POST /v1/auth/dev) is enabled by default via DEV_AUTH_ENABLED=true.

Terminal window
sbt "runMain madrileno.main.MigrateMain"

MigrateMain is the app’s own IOApp — same as bin/migrate-main in the Docker image — so it reads application.conf with .env injected by sbt-dotenv. Works out of the box on the default .env. It takes a subcommand (migrate is the default):

Terminal window
sbt "runMain madrileno.main.MigrateMain info" # list applied / pending migrations
sbt "runMain madrileno.main.MigrateMain validate" # verify checksums match the files
sbt "runMain madrileno.main.MigrateMain clean" # DROP every table — dev DB only

Run migrate every time you add one under src/main/resources/db/migration/.

The recommended workflow is one long-lived sbt session in interactive mode:

Terminal window
sbt

Inside the sbt shell:

> ~reStart

~reStart watches sources and restarts the app on every save (compile-on-save). Plain reStart runs it once. The app comes up on http://localhost:9000 (override with PORT in .env).

Quick smoke test from another terminal:

Terminal window
curl http://localhost:9000/v1/health-check

To check the whole setup at once — Docker, the compose services, Postgres, and the app — run the doctor (needs scala-cli):

Terminal window
./scripts/doctor.scala
  • sbt caches .env at JVM startup. If you change .env, exit sbt and start it again. Same goes for the long-lived sbt server (~/.sbt/1.0/server/...).
  • docker compose down keeps volumes; docker compose down -v wipes them. Wipe when you want a clean PG, change OpenObserve credentials, or cycle a corrupted state.
  • Tests don’t use the docker-compose stack. Testcontainers spins up its own. Don’t worry about polluting your dev DB during a test run.
  • Migrations don’t run automatically when the app starts. Run sbt "runMain madrileno.main.MigrateMain" after adding one or after wiping volumes.
  • OpenObserve creates OTLP streams on first ingest. If the Traces tab is empty right after boot, hit the app a few times, refresh, give it a moment.

Reference material lives in docs/. For day one, read in this order:

Driving development with an AI assistant? docs/ai-assisted-dev.md covers the Metals + madrileno MCP server setup (the build tools CLAUDE.md assumes).

Building a UI against it? docs/frontend.md covers the reference frontend companion and the generated-contract loop that keeps it type-safe.

The docs/README.md lists everything else by topic — stack (auth, scheduler, observability…), conventions (domain modeling, sealed monad, error handling), operations (configuration, deployment).

The template is licensed under Apache-2.0, but projects generated from it are unencumbered: you may relicense the code created via ./scripts/init-project.scala under any terms, with no attribution required. The init script removes the LICENSE file (and this section) so you can add a license of your own choosing.