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

Install and run

FerroTERM ships as a release tarball and as a container image. Each holds two binaries, the server and the build tool, so there is no JVM to install, no Elasticsearch to run, and no database to provision. The registry systems (UCUM, BCP 47, BCP 13, ISO 3166-1) are inside the binary; every other code system is an index you build from a release you are licensed for (Loading code systems).

The first call, with nothing to load

$ docker run --rm -p 8080:8080 ghcr.io/ferrohealth/ferroterm:0.1.8
$ curl 'http://localhost:8080/r4b/CodeSystem/$lookup?system=http://unitsofmeasure.org&code=mg/dL'

The server starts with no index, serves the four registry systems, and answers the lookup with the unit’s canonical form. GET /r4b/metadata?mode=terminology lists what a deployment serves.

Run with Docker Compose

Every release attaches a compose.yaml. With it and the release files of the code systems you hold a licence for, the path is two steps: build an index for each code system once, then serve them. Put the release files in ./releases beside the compose file (or point FERROTERM_RELEASES_DIR at their directory). For SNOMED CT, a licensed download from SNOMED International or your national release centre:

$ curl -LO https://github.com/FerroHEALTH/FerroTERM/releases/latest/download/compose.yaml
$ docker compose run --rm build --rf2 /releases/SnomedCT_Release.zip --out /data/index/snomed
$ docker compose up

SNOMED CT is optional. A deployment that serves LOINC alone builds only that:

$ docker compose run --rm build --loinc /releases/Loinc_2.82.zip --out /data/index/loinc
$ docker compose up

The build service runs ferroterm-build from the same image, and the arguments after build are its own, so every code system in Loading code systems builds the same way: the release directory is mounted read-only at /releases, and each --out names a child of /data/index, which is ./index on the host (or FERROTERM_INDEX_DIR). docker compose run --rm build with no arguments prints the builder’s help. The release mount is read-only, so filling an ICD-11 cache with --icd11-api (which writes into the cache) runs with the binary; a build from a filled cache in ./releases runs here. A zip is unpacked to the container’s tmpfs and gone with it. The service sits under the build profile, so docker compose up never starts it. To serve indexes you built elsewhere, skip the first command and point the variable at an index root: a directory whose child directories are the artifacts, one per release. A root is the layout the sync service writes (one child per release, renamed into place when complete), so a deployment that starts with a root needs no re-layout when the sync is added later. A single artifact directory is accepted too (see Configuration).

$ FERROTERM_INDEX_DIR=/srv/ferroterm/index docker compose up

The file pulls the image of the release it shipped with, mounts the index read-only at /data/index, publishes port 8080 on the loopback interface, and runs the container with every capability dropped and a read-only root filesystem. FERROTERM_BIND_HOST, FERROTERM_PORT, FERROTERM_LOG_FORMAT, FERROTERM_DEFAULT_LANGUAGE, and RUST_LOG are variables you set on the command line.

The ferroterm service declares a health check, so docker compose up --wait returns once the server answers GET /health, and a service of your own waits for it with depends_on: ferroterm: condition: service_healthy. The shipped proxy service does exactly that. See Health, metrics, and request identifiers for what the probe does.

Run the container by hand

The image is ghcr.io/ferrohealth/ferroterm, published for linux/amd64 and linux/arm64 with every release. It holds the static ferroterm and ferroterm-build binaries on a distroless base: no shell, no package manager, a numeric non-root user (65532), and the listen address preset to 0.0.0.0:8080.

$ docker run --rm -p 8080:8080 \
    -v /srv/ferroterm/index:/data/index:ro \
    -e FERROTERM_INDEX=/data/index \
    ghcr.io/ferrohealth/ferroterm:0.1.8

Mount the index root read-only; each child directory under it is one artifact. The server writes nothing while serving, so the container runs with a read-only root filesystem. Tags are <version>, <major.minor>, and latest; a deployment pins the digest and verifies its provenance first (see Verifying releases):

$ gh attestation verify oci://ghcr.io/ferrohealth/ferroterm:0.1.8 \
    -R FerroHEALTH/FerroTERM \
    --signer-workflow FerroHEALTH/FerroTERM/.github/workflows/release-image.yml

The image carries a HEALTHCHECK that runs ferroterm healthcheck: the binary probes GET /health on its own listen address, since there is no shell or curl to do it, so docker inspect reports the container’s health and Compose can wait on it. Kubernetes ignores the image’s HEALTHCHECK: point the readiness and liveness probes at GET /health, set runAsNonRoot: true, readOnlyRootFilesystem: true, drop every capability, and mount the index root (not a single artifact) read-only at the path FERROTERM_INDEX names. The server stops cleanly on SIGTERM.

Run the binary

Each release carries a tarball per target (x86_64 and aarch64, glibc and musl) holding ferroterm and ferroterm-build, with a checksum, a CycloneDX SBOM, and Sigstore attestations beside it. Download, verify, unpack, run:

$ gh release download v0.1.8 -R FerroHEALTH/FerroTERM -p 'ferroterm-v0.1.8-x86_64-unknown-linux-musl.tar.gz*'
$ gh attestation verify ferroterm-v0.1.8-x86_64-unknown-linux-musl.tar.gz -R FerroHEALTH/FerroTERM \
    --signer-workflow FerroHEALTH/FerroTERM/.github/workflows/release-build.yml
$ tar xzf ferroterm-v0.1.8-x86_64-unknown-linux-musl.tar.gz
$ FERROTERM_INDEX=/srv/ferroterm/index ./ferroterm

The server opens each index read-only, refuses to start on a missing or damaged one, and listens on 127.0.0.1:8080. The listen address, the index directories, the FHIR resource directories, and the default display language are environment variables described in Configuration.

Check that it is serving

$ curl http://localhost:8080/health
$ curl 'http://localhost:8080/r4b/metadata?mode=terminology'

The TerminologyCapabilities names every code system and version this deployment serves, with its filters and properties. See The FHIR terminology API for the operations.