Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLI & production checklist

The binary’s command-line surface, what a zero-configuration boot actually gives you, the minimum a production deployment sets, and which material belongs in a mounted file rather than an environment variable. Precedence, the environment-name grammar, and file discovery are on the Configuration reference index.

The command line

Two flags are global — they apply to the server and to every subcommand:

FlagDescription
--config <path>The configuration file to load, overriding the search order. Fatal if missing or unreadable.
--set <key>=<value>A dotted-path override, repeatable, highest precedence of all layers (e.g. --set db.max_connections=40).

With no subcommand, the binary boots the server.

ferroehr config …

ferroehr config default             # print the annotated default ferroehr.toml
ferroehr config check [--config P]  # validate file + environment + --set

config default writes the fully-commented template every key’s default comes from — the starting point for a real ferroehr.toml.

config check runs the same validation the server runs at boot — the strict unknown-key sweep, the type pass, the aggregated semantic rules, and the “authentication enabled needs a mechanism” rule — and touches no database, so it is safe in CI and before a rollout. It exits 0 when the configuration is valid and 1 otherwise; on success it prints the effective configuration as TOML with every secret redacted, and notes on stderr when db.url is still the built-in development default.

ferroehr db …

ferroehr db migrate   # apply the embedded migrations and exit
ferroehr db verify    # verify, issuing no DDL, that the schema matches this build

These exist so a least-privilege deployment can separate the two database identities: run db migrate once under a DSN that holds DDL rights (a Kubernetes Job, an init container, a CI/CD stage), then boot the server with db.migrate = "verify" under a DSN with no DDL rights at all. See Operations.

ferroehr healthcheck

Probes the running server’s status endpoint and exits 0 on a 2xx, 1 otherwise — the container HEALTHCHECK and the Kubernetes exec-probe fallback.

VariableTypeDefaultDescription
FERROEHR_HEALTHCHECK_URLURLhttp://127.0.0.1:8080/ferroehr/rest/statusThe URL the subcommand probes; also settable as --url. Not part of ferroehr.toml.

Zero-config boot and the production checklist

With no file and no environment, the effective configuration is: listener 0.0.0.0:8080 at the ITS-REST base path with Swagger UI; the database at the built-in development DSN with migrations applied at boot; RBAC on; signing on in digest mode with read-time verification strict; the audit trail on with only the local store; rate limiting on; logs in auto format at info; and every integration off.

One thing that configuration does not do is serve requests. auth.enabled defaults to true, and authentication enabled with no mechanism configured is a boot error rather than a running server that refuses everything: RFC 9110 §11.6.1 requires a 401 challenge to name a scheme applicable to the resource, and a server with no mechanism has none — it could only refuse every request while advertising a scheme it does not implement. The error names the three ways out: add [[auth.basic.users]], add an [auth.oidc] issuer, or set auth.enabled = false for development. So a bare docker run of the image with no configuration stops at startup with that message, while the downloadable Compose quickstart boots because it ships a user.

For production, set at least:

  • db.url — the real DSN, via FERROEHR__DB__URL from a secret or a url_file-mounted value, never inline in a world-readable file. Leaving the development default in place is warned about loudly at every boot.
  • db.migrate = "verify" with the schema applied out of band, so the serving role needs no DDL rights.
  • an authentication mechanism — a Basic user store and/or [auth.oidc].
  • log.format = "json" for cluster log collectors.
  • server.cors_permissive stays false; server.swagger_ui per posture.
  • server.system_id — this deployment’s own openEHR system identifier. Choose it before the first EHR is created: it is stored with every EHR, audit entry and version identifier, and changing it later never rewrites what is already committed.
  • management.* per posture. A dedicated management.port is recommended so the introspection surface is never reachable on the clinical listener, and every endpoint stays off until you name a level for it.
  • TLS everywhere a transport supports itserver.tls (or a TLS-terminating ingress), audit.syslog.transport = "tls", events.tls, fhir.outbound.tls, HTTPS for the object store.
  • real secrets via the environment or a *_file sibling, never inline.

What belongs in a mounted file (versus the environment)

The environment cannot carry an array of tables, so the Basic-auth user store ([[auth.basic.users]]) is file-only.

Genuinely file-shaped material — the PGP signing key, Cedar policies, ATNA and terminology-server PEMs, a JWKS blob — is referenced by an in-TOML *_path / *_file key pointing at a mounted path. On Kubernetes the chart’s config.files map materialises each entry under /etc/ferroehr/, read-only, from a Secret.

Prefer a *_file sibling over the environment form for any secret in a container: an environment value is readable through /proc/<pid>/environ and is inherited by every child process the container spawns.

Everything else is a plain key you can set in the file or override with a FERROEHR_* variable.

For a worked development example — the server section, CORS, admin, management and the Basic-auth user store — read the configuration carried inline in the quickstart docker-compose.yml; see Docker Compose.

Variables outside the server’s namespace

The PostgreSQL init container’s variables are PG_INIT_USER, PG_INIT_PASSWORD and PG_INIT_DB — they configure the database container, not the server, and sit outside the server’s reserved FERROEHR_ namespace.

Inside that namespace, a handful of names are deliberately not configuration keys and pass the strict sweep untouched: FERROEHR_CONFIG (the config-file pointer), FERROEHR_HEALTHCHECK_URL (the container healthcheck), the build-stamp variables, and the Compose parameterization (image tags, host ports, CPU and memory limits). They keep a single _ by design, which is exactly what distinguishes them from configuration keys — and why a single-underscore misspelling of a real key is reported at boot with the uniform spelling it should have had.