Skip to content

Config inheritance

A Host has exactly two layers of configuration. There is no hierarchical config tree, no MATRIX_HOME walk-up at the Host level, no .env-style overlays. Just two files per runtime, and the Host writes the lower file deterministically from the upper one.

The two layers

Layer 1: host.json

<host-home>/host.json. Hand-edited. Owned by the operator. Schema is IMatrixHostConfig (see Host config).

This is where transport root, NATS URL, HTTP host/port, auth mode, and storage path overrides live. The Host normalizes it on read (HostStateStore.loadOrCreateConfig, host-state-store.ts:31-44) and writes back any normalization changes. The file is the source of truth for the Host process.

Layer 2: <host-home>/runtime-env/<runtimeId>.environment.json

Generated. Owned by the Host. Never edit by hand — every Host start and every config reload rewrites it.

json
// example
{
  "name": "host",
  "nats": {
    "mode": "external",
    "url": "nats://127.0.0.1:4270",
    "wsUrl": "ws://127.0.0.1:4271",
    "credentialsRef": "file:credentials.json"
  },
  "runtime": {
    "root": "COM.OPEN-MATRIX.LOCAL.AUTHORITY",
    "runtimeId": "RUNTIME-HOST-LOCAL-ABC123-CHAT",
    "runtimeMount": "system.runtimes.RUNTIME-HOST-LOCAL-ABC123-CHAT",
    "controlMount": "system.runtimes.RUNTIME-HOST-LOCAL-ABC123-CHAT.control"
  },
  "host": {
    "matrixDir": "/home/me/.matrix"
  },
  "http": {
    "enabled": true,
    "port": 5183
  }
}

The runtime child process reads this file via the --env-file <path> argument (cli.ts:599-617). Every runtime gets its own file; mounts and the served-app port differ per runtime, but nats and host are identical across all runtimes on one Host.

Field-by-field inheritance

Field in runtime-env/*.environment.jsonComes fromEditable by operator?
name--env <name> flag at matrix up, persisted in recordyes (re-up)
nats.modehard-coded "external" (Host always exposes NATS as external to runtimes)no
nats.urlhost.json transport.nats (or wrapper-supervised sibling URL)yes (host.json)
nats.wsUrl, nats.credentialsRefsameyes
runtime.roothost.json transport.rootyes
runtime.runtimeId, runtimeMount, controlMountcomputed at startno
host.matrixDir<home>yes (move home; rare)
http.host, http.portgateway runtimes: host.json http. Served-app runtimes: per-runtime allocated portyes for gateway; never for served apps

When the Host rewrites runtime-env

MatrixHostService._refreshRuntimeEnvironmentFiles (matrix-host-service.ts:200-232) iterates every persisted record and calls _refreshRuntimeEnvironmentFile for each. That happens:

  1. On every successful start() — the Host overwrites every existing environment file with the current Host status (so even if you edited host.json while the Host was stopped, runtimes get the new values on next start).
  2. On every successful applyUpdatedHostConfig — i.e. after a config.reload RPC.
  3. Lazily on _refreshRuntimeRecordServedAppMetadata, when a runtime's webapp metadata in the record drifts from the environment file's http block.

The actual file content comes from writeRuntimeEnvironmentFile (matrix-host-service.ts:1529-1568), which preserves some per-runtime state (the served-app port the OS allocated) while overwriting NATS, runtime, and host blocks.

Practical consequences

  • host.json is the only file you edit. Everything in runtime-env/ is derived. Touching runtime-env/ files directly will be undone the next time the Host updates them, and there is no guarantee of when that is.
  • config.reload is a real restart. It is not a "hot reload"; it stops every runtime and restarts them with the new config. Worker bootstrap and any in-flight requests are interrupted. See MatrixHostService._applyUpdatedHostConfig at matrix-host-service.ts:180-198.
  • Per-runtime overrides do not exist. If you need one runtime on a different NATS URL, that's a different Host. The Host configuration scope is the home, not the runtime.
  • MATRIX_HOST_HOME env var only selects the home, not config contents. There is no env-var-driven config fallback inside the Host process.

When config comes from somewhere else

A few upstream sources can change host.json-relevant fields, all of them by writing to host.json directly via a documented path:

UpstreamWrites which fieldWhen
hivecast install --root <root>transport.root / authorityRootinstall time
hivecast install --http-port <p>http.portinstall time
hivecast login --device (via system-auth)transport.spaceId, routeKey, publicNamespacepairing
host.control config.reload RPC(none directly — re-reads existing file)runtime

There is intentionally no automatic merging from ~/.matrix/config.d/, no host.json.local, no MATRIX_* env-overlay. Two layers, one source of truth per layer.

See also