Documentation / Exploitation / Référence / Deployment topology

Deployment topology

Source: docker-compose.yml (repository root). This page documents the containerized stack as declared in compose — process list, ports, images, environment, volumes, and the request path from UI to adapter.

Processes

Four processes carry no application state of their own: durable state lives in Restate, and the run index lives in a separate database (SQLite or Postgres — see Run store).

Process Role Port(s) Image / build
restate Durable control plane: journal, replay, run suspension 8080 (ingress), 9070 (admin) docker.restate.dev/restatedev/restate:1.4
endpoint The engine: loads plugins + instance, runs the meridianWorkflow and record Restate services 9080 Dockerfile, command pnpm --filter @meridian/api run restate
proxy REST API: events, runs, records, catalog, spec persistence; serves /healthz and /readyz 4000 Dockerfile, command pnpm --filter @meridian/api run server
ui Design/execution console (Next.js, config-plane) 3000 Dockerfile.ui

Two additional services back a full local stack:

Process Role Port(s) Image / build
ehr-lab Mini-EHR test bench; workflow validations round-trip through FHIR Task, with no direct link to proxy 3200 Dockerfile.ehr-lab
blaze FHIR R4 warehouse backing ehr-lab (directory, records, messaging, audit, validations) 8090→8080 samply/blaze:1.10.1

endpoint and proxy are built from the same Dockerfile and the same workspace; only the command differs. Both run TypeScript sources directly via tsx (no AOT compilation step for the host).

Request flow

UI → proxy → (ingress) restate → endpoint → adapters (FHIR, …)

End-to-end trace shape (see Observability signals):

POST /api/events (proxy)
└─ restate.ingress (proxy)
   └─ workflow.run (endpoint)
      ├─ node <id>  (one span per executed node; map body: node <id>#<i>)
      │  └─ fhir GET|POST|PUT  /  posos.graphql   (external I/O, under the calling node)
      └─ …

endpoint exposes two fixed Restate services (apps/api/src/restate/registry.ts, apps/api/src/restate/services.ts):

Service Handlers Role
meridianWorkflow run, resolveValidation, getRun A single generic durable workflow that interprets whatever WorkflowSpec is passed in its run input — defining or editing a workflow never adds a Restate service.
record snapshot Read-only proxy onto the declared record ports (record: { contextKind } adapters — e.g. patient), exposed over the ingress so proxy can serve GET /api/records/:kind/:id without depending on adapters or the domain directly.

proxy reaches endpoint's services only through the Restate ingress (RESTATE_URL), never by calling endpoint directly.

Compose services

restate

  • Image: docker.restate.dev/restatedev/restate:1.4.
  • RESTATE_NODE_NAME=restate-dev — pinned so the node identity survives container recreation (default is the container hostname/id; a new id on recreation makes Restate refuse the existing restate-data volume with RT0002).
  • Ports: 8080 (ingress), 9070 (admin — deployment registration).
  • extra_hosts: host.docker.internal:host-gateway — lets the container reach an endpoint running on the host in dev mode.
  • Volume: restate-data:/restate-data (see Run store and Back up and restore for what this volume holds).
  • Healthcheck: TCP probe on port 9070.
  • No profiles: key — started by plain docker compose up -d as well as by --profile stack.

endpoint (profile stack)

  • Build: Dockerfile, context ..
  • Command: pnpm --filter @meridian/api run restate.
  • Volume: ./external-plugins:/app/external-plugins:ro — external plugins are mounted from the host, not baked into the image; build them first with pnpm run build:plugins. In a real deployment they instead come from the plugin registry (manifest plugins: + MERIDIAN_REGISTRY — see Set up a plugin registry).
  • Environment: PLUGINS_PATH (points at the three mounted plugin dist/ dirs), INSTANCE_CONFIG (/app/${INSTANCE_FILE:-instance.dev.yaml}), RESTATE_ADMIN_URL=http://restate:9070, RESTATE_ENDPOINT_URL=http://endpoint:9080, LOG_LEVEL, OTEL_EXPORTER_OTLP_ENDPOINT, ANTHROPIC_API_KEY, OPENAI_API_KEY.
  • depends_on: restate with condition: service_healthy.
  • No published ports in compose (reached only via the Restate ingress).
  • On startup, endpoint self-registers against RESTATE_ADMIN_URL (retried; disable with RESTATE_AUTO_REGISTER=false).

proxy (profile stack)

  • Build: Dockerfile, context . (same image as endpoint).
  • Command: pnpm --filter @meridian/api run server.
  • Port: 4000:4000.
  • Environment: PLUGINS_PATH (same three paths as endpoint), RESTATE_URL=http://restate:8080, INSTANCE_CONFIG, RUN_DB_URL (default sqlite:/data/runs.sqlite), LOG_LEVEL, OTEL_EXPORTER_OTLP_ENDPOINT, ANTHROPIC_API_KEY, OPENAI_API_KEY.
  • Volumes: proxy-data:/data (run index, when SQLite), ./external-plugins:/app/external-plugins:ro.
  • depends_on: restate (service_healthy), endpoint (service_started).
  • Healthcheck: GET /healthz on port 4000.

ui (profile stack)

  • Build: Dockerfile.ui, context ..
  • Port: 3000:3000.
  • Environment: API_URL=http://proxy:4000 — resolved server-side at runtime by Next.js rewrites, not baked in at build time.
  • depends_on: proxy with condition: service_healthy.

ehr-lab (profile stack)

  • Build: Dockerfile.ehr-lab, context ..
  • Port: 3200:3200.
  • Environment: FHIR_BASE_URL (default http://blaze:8080/fhir), POSOS_API_URL, POSOS_AUDIENCE, GOOGLE_APPLICATION_CREDENTIALS.
  • depends_on: blaze (service_started).
  • Healthcheck: GET /api/health on port 3200.
  • Workflow validations transit through FHIR Task resources — ehr-lab has no direct dependency on proxy. To exercise workflows end to end against this same warehouse, point endpoint/proxy at it too (INSTANCE_FILE=instance.example.yaml, adapter baseUrl: http://blaze:8080/fhir).

blaze (profile stack)

  • Image: samply/blaze:1.10.1 — pinned (not a moving tag: a version drift once hid a FHIR-search compatibility bug against the deployed warehouse).
  • Environment: STORAGE=standalone (persistent RocksDB, not in-memory), BASE_URL=http://blaze:8080, JAVA_TOOL_OPTIONS=-Xmx2g.
  • Port: 8090:8080.
  • Volume: blaze-data:/app/data.
  • Sole datastore behind ehr-lab: directory, records, messaging, audit, and validation tasks.

lgtm (profile obs)

  • Image: grafana/otel-lgtm:0.8.1 — Tempo + Prometheus + Loki + Grafana, pre-wired, in one container.
  • Ports: 3001:3000 (Grafana UI, remapped to avoid clashing with ui), 4317:4317 (OTLP gRPC), 4318:4318 (OTLP HTTP — used by the app's -http exporters).
  • Volume: lgtm-data:/data.
  • Healthcheck: GET /api/health on port 3000 (in-container).
  • See Enable observability and Observability signals.

postgres (profile pg)

  • Image: postgres:17-alpine.
  • Environment: POSTGRES_USER=clinical, POSTGRES_PASSWORD (default clinical), POSTGRES_DB=clinical.
  • Port: 5432:5432.
  • Volume: pg-data:/var/lib/postgresql/data.
  • Healthcheck: pg_isready -U clinical.
  • Backs RUN_DB_URL=postgres://clinical:clinical@postgres:5432/clinical — see Use the Postgres run index.

Volumes

Volume Mounted by Contents
restate-data restate Durable execution state: journal, run status, suspensions.
proxy-data proxy SQLite run index (RUN_DB_URL=sqlite:/data/runs.sqlite, default).
blaze-data blaze FHIR warehouse storage (RocksDB, standalone mode).
lgtm-data lgtm Tempo/Prometheus/Loki data for the local observability stack.
pg-data postgres Postgres data directory, when the pg profile backs the run index.

Profiles

Profile Services Purpose
(none) restate Dev default: docker compose up -d starts only Restate; endpoint/proxy/ui run on the host via pnpm run dev (or dev:fhir).
stack restate, endpoint, proxy, ui, ehr-lab, blaze Full containerized stack, images built from this repository.
pg postgres Adds a Postgres-backed run index; combine with stack and set RUN_DB_URL.
obs lgtm Adds the local Tempo/Prometheus/Loki/Grafana stack; combine with stack and set OTEL_EXPORTER_OTLP_ENDPOINT=http://lgtm:4318.
75 documents14 sectionssource : /docs · généré au build