Skip to content

Local Development

This runbook reflects the current Docker Compose setup in this repository.

Prerequisites

  • Docker with Compose support
  • Python 3.12+ if you want to run backend tasks outside containers
  • Node.js 20+ if you want to run UI tasks outside containers

Fast Path

Bring the full stack up:

make preflight
make up
make ps

If you switched branches and migrations or seed data changed, reset volumes first:

make down-v
make preflight
make up

make preflight runs three checks before startup:

  • tooling checks (docker, docker compose, compose wrapper)
  • NPM startup decision (NPM_MODE=auto|on|off)
  • env consistency checks for auth hostname/issuer/CORS alignment

Use NPM_MODE=auto to reuse external Nginx Proxy Manager when available. Force behavior when needed:

make up NPM_MODE=off
make up NPM_MODE=on

Default Local Endpoints

  • UI: http://localhost:18080
  • UI HTTPS: https://localhost:18443
  • Backend API: http://localhost:18011
  • Keycloak: http://localhost:18081
  • Nginx Proxy Manager Admin: http://localhost:81 (only if local NPM is started)
  • PostgreSQL: localhost:18432
  • Redis: localhost:18433
  • Neo4j Browser: http://localhost:7474
  • Neo4j Bolt: localhost:7687
  • NATS client: localhost:18004
  • OPA: http://localhost:18007
  • PgAdmin: http://localhost:5050
  • Redis Commander: http://localhost:18083

Health Checks

Use these after bootstrap:

make ps
curl http://localhost:18011/api/v1/config
curl http://localhost:18081/realms/substrate/.well-known/openid-configuration
curl -I http://localhost:18080

Expected local auth behavior:

  • browser-facing issuer matches SUBSTRATE_OIDC_ISSUER_URL
  • backend discovery resolves Keycloak on the Docker network

Environment Model

The local stack intentionally splits public URLs from internal service URLs.

Browser-facing values

These are the URLs a browser or external tool should see:

  • SUBSTRATE_VITE_OAUTH_ISSUER_URL
  • SUBSTRATE_OIDC_ISSUER_URL
  • SUBSTRATE_KEYCLOAK_HOSTNAME
  • SUBSTRATE_KEYCLOAK_UI_BASE_URL
  • SUBSTRATE_CORS_ALLOW_ORIGINS
  • Keycloak client redirect URLs in infra/keycloak/substrate-realm.json

Internal container values

These are Docker-network addresses used between services:

  • SUBSTRATE_OIDC_DISCOVERY_URL
  • SUBSTRATE_DATABASE_URL
  • SUBSTRATE_REDIS_URL
  • SUBSTRATE_NATS_URL
  • SUBSTRATE_NEO4J_URI
  • SUBSTRATE_OPA_URL

For local Docker, the important split is:

  • public issuer: https://auth.invariantcontinuum.com/realms/substrate (default in .env.example)
  • internal discovery: http://keycloak:8080/realms/substrate/.well-known/openid-configuration

Default Variable Source

Defaults live in .env.example.

  • make targets read .env; if missing, it is created from .env.example.
  • Use make preflight, make up, make down, make ps, make logs, make config.
  • Use make render-infra-config if you only want to re-render infra config files.
  • Override any value ad-hoc by prefixing the command, for example:
SUBSTRATE_KEYCLOAK_PORT=28081 SUBSTRATE_UI_PORT=28080 make up NPM_MODE=off
  • Keep backend/.env.backend and ui/.env.ui for optional local overrides only.

Infra Template Rendering

Some infra config files are generated from templates and controlled through Makefile defaults/overrides:

  • infra/keycloak/substrate-realm.jsoninfra/keycloak/substrate-realm.json.template
  • infra/pgadmin/servers.jsoninfra/pgadmin/servers.json.template
  • infra/pgadmin/pgpassinfra/pgadmin/pgpass.template

All make compose targets render every infra/**/*.template file first automatically. Edit template files, not generated output files. If you only want to refresh generated infra configs, run:

make render-infra-config

Running Individual Workflows

Backend tests

cd backend
uv run pytest

UI build or tests

cd ui
npm run build
npm run test:run

Regenerate API contracts and UI types

./api/generate-types.sh

Troubleshooting

Keycloak login redirects are wrong

Check:

  • docker-compose.yml
  • infra/keycloak/docker-compose.yml
  • infra/keycloak/substrate-realm.json
  • backend/.env.backend
  • ui/entrypoint.sh

The usual failure is mixing the public issuer URL with the internal Docker hostname.

Backend starts but auth fails

Check the config endpoint:

curl http://localhost:18011/api/v1/config

The reported issuer should match SUBSTRATE_OIDC_ISSUER_URL.

Branch switch caused migration failures

Reset the local state:

make down-v
make preflight
make up

Frontend boots but API calls fail

Check the UI container env defaults and backend config:

  • ui/entrypoint.sh
  • ui/dockerfile
  • backend/app/settings.py
  • backend/app/core/security.py