Skip to main content

Deployment

The application runs locally via Docker Compose with 14 services. This page documents the current setup and the production deployment roadmap.

Docker Compose Architecture

Service Configuration

ServiceImagePortsDepends OnHealth Check
frontend./frontend (multi-stage build)3001backend (service_healthy)HTTP GET /api/health
backend./backend (multi-stage build)8002temporal, postgres, minio (service_healthy)HTTP GET /health
worker./backend (multi-stage build)-- (no ports)temporal, postgres, minio (service_healthy)Process check
temporaltemporalio/auto-setup:latest7233postgres (service_healthy)TCP port 7233
temporal-uitemporalio/ui:latest8080temporal (service_healthy)HTTP GET /
postgrespostgres:165432--pg_isready
neo4jneo4j:2026-community7474, 7687--cypher-shell
miniominio/minio:latest9000, 9001--mc ready local
redisredis:8-alpine6379--redis-cli ping
keycloakquay.io/keycloak/keycloak:26.08180 (host → 8080)postgres (service_healthy)HTTP health
lettaletta/letta:latest8283postgresHTTP health
clickhouseclickhouse/clickhouse-server:248123--clickhouse-client
langfuse / langfuse-workerlangfuse/langfuse:3.155.13002 (host → 3000)clickhouse, postgresHTTP health

Port Assignments

PortServiceNotes
3001Frontend (Next.js)Ports 3000 reserved for other applications
8002Backend (FastAPI)Port 8000 reserved for other applications
7233Temporal ServergRPC endpoint
7474Neo4j BrowserWeb UI for graph exploration
7687Neo4j BoltBolt protocol for driver connections
8080Temporal UIWeb dashboard for workflow monitoring
5432PostgreSQLShared between Temporal internal DB and application DB
9000MinIO APIS3-compatible endpoint
9001MinIO ConsoleWeb UI for storage management
6379RedisCache for PEPPOL and inhoudingsplicht results
8180KeycloakOIDC identity provider (container port 8080)
8283LettaAgent memory server
8123ClickHouseLangfuse analytics store
3002LangfuseLLM observability UI (container port 3000)
warning

Ports 3000 and 8000 are intentionally avoided. These are reserved for other applications running on the same development machine.

Startup

Docker Compose (Full Stack)

docker-compose up -d

This starts all 14 services. The database schema is automatically initialized via the init_db.sql script mounted into PostgreSQL's docker-entrypoint-initdb.d.

For active development, run the infrastructure in Docker and the application locally:

# Start infrastructure only
docker-compose up -d postgres temporal temporal-ui minio redis

# Start backend (in one terminal)
./start-server.sh
# Equivalent to: cd backend && uvicorn app.main:app --host 0.0.0.0 --port 8002 --reload

# Start worker (in another terminal)
cd backend && python -m app.worker

# Start frontend (in another terminal)
./start-client.sh
# Equivalent to: cd frontend && npm run dev

Database Initialization

The schema is defined in scripts/init_db.sql:

# Manual initialization (if not using docker-compose mount)
psql -U temporal -h localhost -f scripts/init_db.sql

The SQL file is idempotent -- it uses CREATE TABLE IF NOT EXISTS and DO $$ ... END $$ blocks for safe re-execution.

Seed Data

Demo data can be loaded for presentations via the seed scripts in backend/scripts/ (e.g. seed_showcase.py, seed_graph_demo.py, seed_lex_corpus.py, seed_all_regulatory.py). Tenant and demo-case seeding (including the AWDC demo tenant) is also handled by Alembic migrations 052_seed_digiteal_tenant, 055_seed_awdc_tenant, and 056_seed_awdc_demo_cases:

cd backend && python scripts/seed_showcase.py

Environment Variables

Backend (.env)

# Required
DATABASE_URL=postgresql+asyncpg://temporal:temporal@localhost:5432/trustrelay
TEMPORAL_HOST=localhost
TEMPORAL_PORT=7233
MINIO_ENDPOINT=localhost:9000

# AI (required for real agent execution)
OPENAI_API_KEY=sk-...
LLM_API_KEY=sk-...

# External services (optional, mock mode works without these)
BRIGHTDATA_API_TOKEN=...
TAVILY_API_KEY=...
NORTHDATA_API_KEY=...

# Mock modes — all default to FALSE (real data sources are the default).
# Set individual flags to true to run that source against hardcoded demo data.
OSINT_MOCK_MODE=false
MCC_MOCK_MODE=false
TASK_GENERATOR_MOCK_MODE=false
DOC_VALIDATION_MOCK_MODE=false
BELGIAN_MOCK_MODE=false
PEPPOL_MOCK_MODE=false
BRIGHTDATA_MOCK_MODE=false
TAVILY_MOCK_MODE=false

# Authentication (Keycloak OIDC, port 8180)
AUTH_JWKS_URL=http://localhost:8180/realms/trust-relay/protocol/openid-connect/certs
AUTH_ISSUER=http://localhost:8180/realms/trust-relay
AUTH_AUDIENCE=trust-relay-app
KEYCLOAK_URL=http://localhost:8180

Frontend (.env.local)

NEXT_PUBLIC_API_URL=http://localhost:8002

Persistent Volumes

Docker Compose uses named volumes for data persistence:

VolumeMounted AtPurpose
postgres_data/var/lib/postgresql/dataDatabase files
minio_data/dataUploaded documents and evidence
redis_data/dataCache persistence

CI/CD Pipeline (GitHub Actions)

The project has a GitHub Actions CI pipeline (.github/workflows/ci.yml) that runs on every push and pull request to master:

4 CI Jobs

JobWhat It DoesKey Details
backend-testsRuns pytest with coveragePostgreSQL 16 + Redis 8 service containers, --cov-fail-under=70
frontend-testsRuns Jestnpm test -- --watchAll=false --ci
lintRuff (backend) + ESLint (frontend)Runs in parallel with tests
buildDocker Compose buildRuns after all 3 previous jobs pass

The pipeline uses concurrency groups (ci-${{ github.ref }}) to cancel superseded runs automatically. All mock modes are enabled in CI to avoid external API calls.

Docker Health Checks

All services have HEALTHCHECK directives with condition: service_healthy dependencies:

backend:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8002/health"]
interval: 10s
timeout: 5s
retries: 5
depends_on:
postgres:
condition: service_healthy
temporal:
condition: service_healthy
minio:
condition: service_healthy

This ensures services only start after their dependencies are healthy, eliminating race conditions during startup.

Multi-Stage Docker Builds

Both backend and frontend use multi-stage Dockerfiles:

  • Backend: python:3.13-slim base (builder + runtime stages), installs only production dependencies, wires the packages/ shared monorepo
  • Frontend: Build stage with npm run build, production stage with standalone Next.js output

This reduces image sizes and eliminates development dependencies from production containers.

Testcontainers

20 integration tests use Testcontainers for isolated PostgreSQL instances per test run. macOS Docker Desktop is automatically detected via ~/.docker/run/docker.sock:

@pytest.fixture
async def integration_db():
async with PostgresContainer("postgres:16") as pg:
engine = create_async_engine(pg.get_connection_url())
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with async_sessionmaker(engine)() as session:
yield session

In CI, GitHub Actions service containers serve the same purpose with less overhead. Testcontainers is primarily used for local development testing where Docker Desktop provides the container runtime.

Production Infrastructure Enhancements

The following enhancements are planned for the production deployment phase:

  • Log aggregation: Structured JSON logging with correlation IDs, forwarded to a log aggregation service (ELK, Datadog, or similar)
  • HTTPS termination: TLS termination at the load balancer or reverse proxy layer

Production Deployment Roadmap

PhaseInfrastructureStatus
Phase 1GitHub Actions CI (test + lint + build)Done
Phase 2Multi-stage Docker builds, health checksDone
Phase 3Kubernetes deployment (Helm charts)Planned
Phase 4TLS termination, load balancer, DNSPlanned
Phase 5Log aggregation, APM, alertingPlanned
Phase 6Temporal Cloud (managed) or self-hosted Temporal clusterPlanned

Temporal Cloud Option

For production, Temporal Cloud eliminates the operational burden of running and scaling the Temporal server. The only change required is updating the connection configuration:

# From self-hosted:
temporal_host: str = "localhost"
temporal_port: int = 7233

# To Temporal Cloud:
temporal_host: str = "your-namespace.tmprl.cloud"
temporal_port: int = 7233
# + mTLS certificate configuration