Environment variables
This page lists every environment variable read by the Meridian runtime processes, the process(es) that read each one, its role, and its default. It mirrors the code; where a variable's behaviour matters, the source file is cited.
The processes referred to below are:
| Process | Entry point | Log/service name | Default PORT |
|---|---|---|---|
| proxy (runtime API) | apps/api/src/server/api.ts |
meridian-proxy |
4000 |
| endpoint (Restate services) | apps/api/src/restate/server.ts |
meridian-endpoint |
9080 |
| ui (ehr-lab mini-EHR) | apps/ehr-lab (Next.js) |
— | 3000 |
For the deployment topology these processes form, see Topology.
.env loading
Both proxy and endpoint import apps/api/src/env.ts first. It reads a .env
file at the repository root (path resolved relative to the module) if one
exists, and copies each KEY=value line into process.env only when the key
is not already set — an explicit export FOO=bar in the shell always wins
(apps/api/src/env.ts). Lines starting with # and malformed keys are ignored;
surrounding single or double quotes on the value are stripped. There is no
dotenv dependency.
The ehr-lab app loads the same repository-root .env through its own loader
(apps/ehr-lab — src/infrastructure/env.ts).
Consequences:
- In a checkout, copy
.env.exampleto.envat the repo root and restart the process after any change. - In a container/Kubernetes deployment, inject variables through the service
environment (compose
env_file/environment, or aSecret) rather than copying a.env. Instance manifests are version-controlled and must never contain secrets — see Security posture.
Instance and plugin resolution
Read by both proxy and endpoint at boot.
| Variable | Process(es) | Role | Default |
|---|---|---|---|
INSTANCE_CONFIG |
proxy, endpoint | Path to the instance manifest. Relative paths resolve from the repo root. Absent → instance.yaml at the repo root; if that is also missing, an empty instance (all-internal/in-memory dev default). A path that is set but missing fails the boot loudly. |
instance.yaml |
PLUGINS_PATH |
proxy, endpoint | Extra roots holding external compiled plugins, split on , or the OS path delimiter. Relative entries resolve from the process cwd. |
(empty) |
MERIDIAN_REGISTRY |
proxy, endpoint | Default plugin registry used to resolve instance-declared plugins when the manifest has no registry: key. Accepts gs://bucket[/prefix], file://…, or a repo-relative directory. |
(none) |
MERIDIAN_PLUGIN_CACHE |
proxy, endpoint | Local cache directory for plugins downloaded from the registry. Relative paths resolve from the repo root. | .data/plugins |
The manifest's own registry: key takes precedence over MERIDIAN_REGISTRY; a
plugin listed with a local path bypasses the registry entirely
(apps/api/src/registry.ts). A declared plugin that cannot be resolved fails the
boot. See
Set up a plugin registry,
Declare and install plugins,
and the Instance manifest reference.
Restate wiring
| Variable | Process(es) | Role | Default |
|---|---|---|---|
RESTATE_URL |
proxy | Restate ingress URL. Required — the proxy throws at startup if it is unset, because execution runs through Restate. A trailing slash is stripped. | (required) |
RESTATE_ADMIN_URL |
endpoint | Restate admin API used for auto-registration (POST {admin}/deployments). A trailing slash is stripped. |
http://localhost:9070 |
RESTATE_ENDPOINT_URL |
endpoint | URI under which the Restate server reaches this endpoint (advertised at registration). | http://host.docker.internal:<endpoint PORT> |
RESTATE_AUTO_REGISTER |
endpoint | Set to the literal false to disable auto-registration; any other value (or unset) leaves it enabled. |
(enabled) |
API_INTERNAL_URL |
endpoint | Where the endpoint reaches the REST proxy to ingest schedule ticks (trigger.schedule, service meridianScheduler). Must point at the proxy in any multi-container deployment, or planned workflows never start. |
http://localhost:4000 |
The endpoint (re)registers its deployment on every start, idempotently
(force: true) and with retry (up to ~3 min), so a manual registration step is
not needed and the endpoint may start before the Restate server
(apps/api/src/restate/register.ts). The registered services are generic and
fixed (meridianWorkflow, record, meridianScheduler), so creating or editing
a workflow never requires re-registration. See
Restate integration
and Why durable execution.
Run index (run store)
Read by the proxy.
| Variable | Process(es) | Role | Default |
|---|---|---|---|
RUN_DB_URL |
proxy | Backend for the persistent run index. sqlite:<path> (local / single node) or postgres://… / postgresql://… (deployment / multiple proxy replicas). An unrecognised prefix throws. |
sqlite:.data/runs.sqlite |
DEFINITION_STORE_URL |
endpoint, proxy | Required (postgres://…): the workflow/agent definition store. Falls back to RUN_DB_URL when that one is Postgres — the tables are disjoint, one database is enough. Boot fails with an explicit message if neither is set. See Manage workflow storage. |
|
DEFINITION_SEED_DIR |
proxy | Directory of the host's system definitions, imported as published v1 when the store is empty (default: apps/api — it carries only agent-invoke.workflow.yaml). Shipped content (demos, business workflows) is seeded through the instance manifest's seed: field instead — see Instance manifest. |
|
DEFINITION_SEED_ALWAYS |
proxy | 1 re-publishes the seed directory at every boot (idempotent by content hash), so editing a YAML and restarting puts it live. Development only — elsewhere it would overwrite definitions edited in the console. |
The default path is relative to the process cwd, so set it explicitly (absolute
path / mounted volume) in a deployment. The index only lists the runs the proxy
started; the durable source of truth is Restate, so losing the index erases no
runs (apps/api/src/server/run-store.ts). The SQLite driver
(better-sqlite3) and the Postgres driver (pg) are imported lazily, so only
the selected one is loaded. See Run store,
Use a Postgres run index, and
Back up and restore.
Ports and inter-process URLs
| Variable | Process(es) | Role | Default |
|---|---|---|---|
PORT |
proxy, endpoint, ui | Listening port. | 4000 (proxy) / 9080 (endpoint) / 3000 (ui) |
API_URL |
ui | Proxy base URL used by ehr-lab for server-side requests. | (deployment-specific) |
The endpoint's PORT also feeds the default RESTATE_ENDPOINT_URL
(apps/api/src/restate/server.ts, register.ts). See
Configure ports and adapters.
Logging
Read by both proxy and endpoint via apps/api/src/observability/logger.ts
(pino).
| Variable | Process(es) | Role | Default |
|---|---|---|---|
LOG_LEVEL |
proxy, endpoint | Pino level: trace, debug, info, warn, error. |
info |
LOG_PRETTY |
proxy, endpoint | 1 forces human-readable output; 0 forces JSON. |
Pretty when stdout is a TTY and NODE_ENV !== "production", JSON otherwise |
SERVICE_NAME |
proxy, endpoint | Service field stamped on log lines. Also set by each entry point via initLogger. |
meridian |
Structured logs never contain patient payloads; a redaction list additionally
censors common secret fields (*.authorization, *.apiKey, *.token, …).
Observability (OpenTelemetry)
Read by both proxy and endpoint via apps/api/src/observability/otel.ts. All
OTel export is opt-in: with no OTLP endpoint set, the SDK is not started and
the metric instruments are zero-cost no-ops.
| Variable | Process(es) | Role | Default |
|---|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
proxy, endpoint | OTLP/HTTP collector endpoint (e.g. http://collector:4318). Presence enables traces + metrics + log export. |
(unset → export disabled) |
OTEL_SERVICE_NAME |
proxy, endpoint | Overrides the OTel service.name resource attribute. |
meridian-proxy / meridian-endpoint |
The OTel service.version resource attribute is taken from
npm_package_version (set by the package manager), defaulting to 0.0.0. When
JSON logging is active and an OTLP endpoint is configured, logs are also shipped
over OTLP. See
Enable observability and
Observability signals.
LLM providers
The shared low-level client (libs/llm-client/src/index.ts,
@meridian/llm-client) resolves a provider's base URL and API key from the
environment. It backs both the editor's workflow-generation assistant
(llmComplete) and the agent runner (plugin @posos/llm). A provider is
considered configured only when its API key is present; the assistant throws if
called for an unconfigured provider, and an agent falls back to a mock runner
when no key is set.
| Variable | Process(es) | Role | Default |
|---|---|---|---|
ANTHROPIC_API_KEY |
proxy, endpoint | Anthropic API key. | (none) |
ANTHROPIC_BASE_URL |
proxy, endpoint | Anthropic base URL. | https://api.anthropic.com |
OPENAI_API_KEY |
proxy, endpoint | OpenAI API key. | (none) |
OPENAI_BASE_URL |
proxy, endpoint | OpenAI base URL. | https://api.openai.com/v1 |
GEMINI_API_KEY |
proxy, endpoint | Google Gemini API key (OpenAI-compatible endpoint). Falls back to GOOGLE_API_KEY. |
(none) |
GOOGLE_API_KEY |
proxy, endpoint | Fallback key for the gemini provider. |
(none) |
GEMINI_BASE_URL |
proxy, endpoint | Gemini base URL. | https://generativelanguage.googleapis.com/v1beta/openai |
MISTRAL_API_KEY |
proxy, endpoint | Mistral API key (OpenAI-compatible). | (none) |
MISTRAL_BASE_URL |
proxy, endpoint | Mistral base URL. | https://api.mistral.ai/v1 |
AZURE_OPENAI_API_KEY |
proxy, endpoint | Azure OpenAI API key. | (none) |
AZURE_OPENAI_BASE_URL |
proxy, endpoint | Azure OpenAI deployment URL (full URL of the deployment). | (empty) |
LOCAL_LLM_API_KEY |
proxy, endpoint | API key for a local OpenAI-compatible server (Ollama / vLLM / LM Studio). | local |
LOCAL_LLM_BASE_URL |
proxy, endpoint | Base URL of the local LLM server. | http://localhost:11434/v1 |
Workflow assistant
Read by the proxy for the editor's AI assistant endpoint
(apps/api/src/server/api.ts).
| Variable | Process(es) | Role | Default |
|---|---|---|---|
ASSIST_PROVIDER |
proxy | Provider used by the workflow-generation assistant. | anthropic |
ASSIST_MODEL |
proxy | Model used by the assistant. | claude-sonnet-4-5 |
See Author an agent and Use the AI assistant.
Bundled-adapter fallbacks
These are read by adapters shipped with the bundled plugins only when the
corresponding value is absent from the instance manifest's adapter config;
config.baseUrl (etc.) in the manifest takes precedence. They apply to whichever
process resolves the port (typically the endpoint for execution ports, the proxy
for terminology). See Bundled plugins.
| Variable | Process(es) | Role | Default |
|---|---|---|---|
FHIR_BASE_URL |
proxy, endpoint | Base URL of the FHIR R4 server used by the @posos/fhir adapters when config.baseUrl is not provided. |
(none — error if neither is set) |
HERMES_BASE_URL |
proxy, endpoint | SNOMED CT terminology server (Hermes) used by the @posos/snomed adapter when config.baseUrl is not provided. |
http://localhost:8081 |
NOTIFY_WEBHOOK_URL |
proxy, endpoint | Target URL for the @posos/notify webhook adapter when config.url is not provided. |
(none — error if neither is set) |
See Connect a FHIR warehouse and Configure terminology.
Posos plugin (@posos/posos)
Read by the external Posos plugin client (external-plugins/posos/lib/client.ts)
in whichever process loads it.
| Variable | Process(es) | Role | Default |
|---|---|---|---|
POSOS_API_URL |
proxy, endpoint | Posos edge API base URL. | https://api.preprod.posos.co |
POSOS_AUDIENCE |
proxy, endpoint | OAuth client ID (IAP audience) for authenticated Posos calls. | (preprod IAP audience) |
POSOS_REGION |
proxy, endpoint | Locale/region selector; fr-BE selects the Belgian client, anything else defaults to fr-FR. |
fr-FR |
GOOGLE_APPLICATION_CREDENTIALS |
proxy, endpoint | Google service-account key file, read by google-auth-library for IAP auth (also honours Application Default Credentials / Workload Identity Federation). |
(none) |
ehr-lab (mini-EHR)
Read server-side by the ehr-lab app (apps/ehr-lab/.env.example); none are
exposed to the browser. In development the localhost defaults apply; on a remote
host they become required and must be injected through the service environment.
| Variable | Process(es) | Role | Default |
|---|---|---|---|
FHIR_BASE_URL |
ui | FHIR warehouse (Blaze) — the app's sole datastore (records, directory, messaging, audit, documents). | http://localhost:8090/fhir (dev) |
NODE_ENV |
ui, proxy, endpoint | Standard Node environment; affects the logger's pretty/JSON default and the ehr-lab runtime. | (unset) |
GOOGLE_APPLICATION_CREDENTIALS |
ui | Google service-account key (JSON) enabling Posos autocompletion, MedicalDB (BIM) dosage suggestions, Scan&Go partner-connect auth, and Google FHIR warehouse access. | (none) |
POSOS_API_URL |
ui | Posos edge API base URL (autocomplete + bim-v2 GraphQL). |
https://api.preprod.posos.co |
POSOS_AUDIENCE |
ui | IAP audience; gates drug autocompletion and BIM dosage suggestions (labelled demo mode when absent); shared fallback for SCANGO_AUDIENCE. |
(preprod) |
SCANGO_BASE |
ui | Scan&Go partner-connect base URL. | https://partner-connect.preprod.posos.co |
SCANGO_COMPLETE_ORIGIN |
ui | Expected postMessage origin for Scan&Go completion. |
https://scan-complete.preprod.posos.co |
SCANGO_AUDIENCE |
ui | IAP audience for Scan&Go. | POSOS_AUDIENCE |
SCANGO_FACILITY_SLUG |
ui | Target fhirStore in the Google warehouse. |
hopital-demo |
SCANGO_FHIR_BASE |
ui | Google Healthcare fhirStores base URL. |
(none) |
SCANGO_CREDENTIALS |
ui | Service-account key file for Scan&Go. | GOOGLE_APPLICATION_CREDENTIALS |
The ehr-lab app does not talk to the proxy directly: clinical validations pass
through the FHIR warehouse (a Task), and the two apps share only Blaze
(FHIR_BASE_URL). See
Connect a FHIR warehouse,
Scan&Go / FHIR mapping (apps/ehr-lab/docs/resources/reference/fhir-mapping.md), and
Set the instance locale.
Related references
- Instance manifest — non-secret, version-controlled configuration.
- Topology — how proxy, endpoint, Restate, and datastores fit together.
- Run store — the persistent run index selected by
RUN_DB_URL. - Observability signals — what the OTel exporters emit.
- Security posture — secrets vs. version-controlled config.