Skip to content

Local development stack

This guide gets a complete SpeculaPro environment running on one computer: the database, the event log, the cache and the application. It runs SpeculaPro from its source repository, which is private, so it is for SpeculaPro’s own engineers and partners with access to the code.

Install:

  • Docker (Docker Desktop, OrbStack or Docker Engine with the Compose plugin)
  • Go 1.26 or newer. The build uses Go 1.27.2, named in backend/go.mod; with an older Go the go command downloads 1.27.2 by itself, unless you have set GOTOOLCHAIN=local
  • Node.js 22 or later and npm
  • make (preinstalled on macOS and most Linux distributions)

Check that ports 5432 (Postgres), 6379 (Valkey), 19092 (Redpanda), 8081 (Redpanda Console) and 8080 (SpeculaPro) are free. If you already run Postgres or Redis locally, stop them first or the containers will fail to start.

  • Directorybackend/ the Go service and its database migrations
    • Directorycmd/speculapro/ the speculapro command
      • …
    • Directorymigrations/ database schema, built into the binary
      • …
  • Directoryfrontend/ the web app; compiled into the binary
    • …
  • Directorydeploy/
    • compose/docker-compose.yml local Postgres, Redpanda, Valkey
    • sql/roles.sql database roles for production
  • Directorydocs/ this documentation site
    • …
  • .env.example sample settings for local development
  • Makefile shortcuts for every step below

Every step below is a make target. To see all of them, with one line on what each does:

Terminal window
make help
  1. Create your settings file. From the repository root:

    Terminal window
    cp .env.example .env

    The Makefile reads .env automatically. The defaults point at the local containers, so you do not need to change anything yet. Every setting is explained in the configuration reference.

  2. Start the supporting services.

    Terminal window
    make up

    This starts four containers in the background: Postgres 18, Redpanda (Kafka-compatible), Redpanda Console (a web page for looking inside Redpanda, at http://localhost:8081) and Valkey (Redis-compatible). The first run downloads the images and can take a few minutes.

    Check that they are running:

    Terminal window
    make ps

    All four should show running, and postgres should show healthy after a few seconds.

  3. Create the database tables.

    Terminal window
    make migrate

    This applies every migration in backend/migrations/ in order. You should see one line per migration file, each marked as applied. Running it again is safe: migrations that are already applied are skipped. make migrate-status lists every migration and whether it is applied.

    If you have set up the separate database roles locally, put the owner’s connection string in SP_DB_OWNER_URL (in .env or the environment): make migrate and make migrate-status then connect as the owner instead of SP_DB_URL. With the default local settings there is one superuser and nothing to set.

  4. Create the Kafka topics.

    First open .env and set a small partition count for a laptop:

    Terminal window
    SP_KAFKA_PARTITIONS=6

    SpeculaPro creates 16 topics, and each gets this many partitions. The default of 64 makes 1,024, which is more than the local single-core Redpanda container accepts (see Partitions). The number is fixed when a topic is created, so set it before this step. Then:

    Terminal window
    make topics

    Expected output (topic names start with the sp. prefix):

    created sp.alert.created
    created sp.alert.created.dlq
    created sp.case.events
    ...

    Each topic gets a .dlq twin (a dead-letter queue, where messages that cannot be processed are parked instead of lost). Running the command again prints nothing: existing topics are left alone.

  5. Build the web app.

    Terminal window
    make web

    This installs the frontend’s packages and compiles the web app into backend/internal/web/dist, where the Go binary picks it up. You only need to repeat this when the frontend changes.

  6. Start SpeculaPro.

    Terminal window
    make run

    You should see log lines similar to:

    level=WARN msg="using the built-in DEVELOPMENT encryption keys; never use this environment for real data"
    level=INFO msg="persister started" persist_worker=0 topic=sp.txn.ingested
    level=INFO msg="stream evaluator started" stream_worker=0 topic=sp.txn.ingested
    level=INFO msg="worker started" interval=6h0m0s
    level=INFO msg="metrics listening" addr=:9090
    level=INFO msg="webhook dispatcher started"
    level=INFO msg="speculapro started" roles="[api decision ingest persist stream worker]" version=dev env=development
    level=INFO msg="http listening" addr=:8080

    The order of the lines can vary, and you will see one persister started line per persist worker and one stream evaluator started line per stream evaluator (four of each by default). The warning is expected in development. It is your reminder that this environment’s encryption keys are public.

    make run starts every role in one process: the web app and API, the real-time decision API, the ingest API, the persister, the stream evaluator and the worker.

    No alerts until you enable rules. A new workspace has no rules. Transactions you send are stored, but nothing is evaluated (and no running totals are kept) until you install a rule pack and enable at least one rule. See Managing rules.

  7. Open the app. Go to http://localhost:8080. The interactive API documentation is at http://localhost:8080/api/docs.

A workspace is one institution’s private area in SpeculaPro: its users, rules, alerts and data. Nobody can sign in until a workspace and its first administrator exist. Create one from the command line:

Terminal window
cd backend
go run ./cmd/speculapro tenant create \
--slug acme \
--name "Acme Pay" \
--kind psp \
--country GH \
--currency GHS \
--timezone Africa/Accra \
--admin-email [email protected] \
--admin-name "Ada Mensah"

From the repository root, make tenant runs the same command, with the options in ARGS:

Terminal window
make tenant ARGS="--slug acme --name 'Acme Pay' --kind psp --country GH --currency GHS --admin-email [email protected] --admin-name 'Ada Mensah'"

Any other command works the same way through make sp, for example make sp ARGS="tenant reset-password --slug acme --email [email protected]".

The output looks like this:

workspace "acme" created (0199c3a2-…)
temporary password (shown once, valid for 72h0m0s, must be changed at first sign-in): 4kQ…
Option Required Meaning Default
--slug Yes Short workspace name typed at sign-in. Lowercase letters, digits and hyphens, 2–63 characters, starting with a letter or digit
--name Yes The institution’s display name
--admin-email Yes Email address of the first administrator
--admin-name No Display name of the first administrator Administrator
--kind No psp, fintech, bank, mobile_money, microfinance or other psp
--country No Two-letter ISO country code GH
--currency No Base currency (three-letter ISO code). Thresholds and reports are expressed in it GHS
--timezone No IANA timezone. Rules such as “per calendar day” use it Africa/Accra

Then sign in at http://localhost:8080 with the workspace slug, the admin email and the temporary password. You will be asked to choose a new password, set up an authenticator app and save your recovery codes. Until the administrator has chosen their own password, their account’s status is invited.

If the first administrator’s password expires

Section titled “If the first administrator’s password expires”

Inside the app, only an administrator can issue a temporary password. If the only administrator can’t sign in (their first temporary password expired, was lost, or they forgot their password), issue a new one from the command line. This is the operator’s break-glass:

Terminal window
cd backend
go run ./cmd/speculapro tenant reset-password --slug acme --email [email protected]
temporary password for [email protected] (shown once, valid for 72h0m0s, must be changed at sign-in): Zt8…
Option Required Meaning
--slug Yes The workspace’s slug
--email Yes The person’s sign-in email

The command works for anyone in the workspace, not only administrators. It:

  • replaces their password with the new temporary one, valid for 72 hours;
  • signs them out everywhere and unlocks the account;
  • keeps their roles and their authenticator app. It doesn’t reset two-factor; once they can sign in, another administrator can do that from Settings › People;
  • leaves a disabled account disabled. Re-enable it in the app first if that person should get back in;
  • is recorded in the audit log as user.reset_password with the actor system and via: cli.

It connects to the database with SP_DB_URL, like tenant create. If the workspace or email doesn’t exist, it fails with find user … in … and changes nothing.

With a workspace in place you can send transactions, customers and accounts through the ingest API, using an API key with the ingest:write scope. Follow Sending data for the steps. To fill the local stack with realistic mobile money traffic in one go, use the load generator.

How to work on the web app with live reload

Section titled “How to work on the web app with live reload”

If you are changing the frontend, run the Vite development server alongside the backend:

Terminal window
make run # in one terminal: API on :8080
make dev-web # in another terminal: UI on :5173

Open http://localhost:5173. Changes to the frontend appear instantly, and API calls are forwarded to port 8080.

Terminal window
make down

stops the containers but keeps your data (it lives in Docker volumes). To start completely fresh, delete the volumes too:

Terminal window
docker compose -f deploy/compose/docker-compose.yml down -v
Symptom Likely cause and fix
make up fails with “port is already allocated” Another program is using 5432, 6379, 19092, 8081 or 8080. Stop it, or change the port mapping in docker-compose.yml and the matching setting in .env
make migrate fails with “connection refused” Postgres is still starting. Wait a few seconds and run it again, or check make ps
The browser shows UI not built: run make web The web app has not been compiled into the binary. Run make web, then restart make run
make topics creates some topics, then fails on one The broker has run out of partitions: 16 topics × SP_KAFKA_PARTITIONS is more than it accepts. Set SP_KAFKA_PARTITIONS=6 in .env. Topics already created keep their size; to start over locally, see How to stop and reset. See Partitions
make topics hangs or fails to connect Redpanda is not running, or SP_KAFKA_BROKERS does not point at localhost:19092. See Kafka troubleshooting
unknown role "…" SP_ROLES or --role has a typo. Use all, or any of api, decision, ingest, persist, stream, worker

More in Troubleshooting.