Installation

Note

If you don’t already have a license, you can request a license key at https://open-fhir.com#access. License will be provided automatically to you via email.

Docker

openFHIR can be easily installed using Docker. The official Docker image is available at openfhir/openfhir-enterprise:latest (build tag is also available).

To run Open-FHIR using Docker, use the following command:

docker run -e SPRING_DATA_MONGODB_URI=mongodb://your-mongodb-url \
           -e OPENFHIR_DB_TYPE=mongo \
           -e OPENFHIR_LICENSE=/path/to/license \
           -p 8080:8080 openfhir/openfhir-enterprise:latest

Database

At the moment, MongoDB and PostgreSQL are supported. There is no default: set openfhir.db.type (OPENFHIR_DB_TYPE) to mongo or postgres, otherwise the engine does not start (see Startup checks).

MongoDB

-e OPENFHIR_DB_TYPE=mongo or in docker compose

services:
  openfhir:
    ....
    environment:
        SPRING_DATA_MONGODB_URI: "mongodb://openfhir:openfhir@mongodb:27017/openfhir"
        OPENFHIR_DB_TYPE: "mongo"

Postgres

-e OPENFHIR_DB_TYPE=postgres or in docker compose

services:
  openfhir:
    ....
    environment:
        SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/openfhir
        SPRING_DATASOURCE_USERNAME: postgres
        SPRING_DATASOURCE_PASSWORD: postgres
        OPENFHIR_DB_TYPE: "postgres"

Connections to PostgreSQL are pooled (HikariCP, pool openfhir-postgres). The pool is tuned with the standard Spring Boot spring.datasource.hikari.* properties, e.g. SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE (default 10) or SPRING_DATASOURCE_HIKARI_CONNECTION_TIMEOUT (milliseconds, default 30000). Every node keeps its own pool, so in a multi-node deployment (see High Availability) the number of nodes times the maximum pool size must stay below the server’s max_connections (100 by default in PostgreSQL), leaving room for other clients.

Database stores the following state:

  • bootstrapEntity: ledger of all files that were bootstrapped, recording each file’s path, a hash of the content that was applied and the entity it created, so that a re-run can tell an unchanged file (skipped) from a changed one (updated in place) and a new one (created). See more: State configuration

  • conceptMapFhirEntity: concept maps for terminology (see more: Terminology)

  • fhirConnectContextEntity: FHIR Connect context mappers

  • fhirConnectMapperEntity: FHIR Connect model mappers

  • optEntity: Operational templates (unless the engine is integrated to an external openEHR repository, see more: Integrations)

Example docker compose - postgres

services:
  openfhir:
    image: openfhir/openfhir-enterprise:latest
    restart: always
    network_mode: host
    container_name: openfhir
    ports:
      - "8080:8080"
    volumes:
      - "/opt/openfhir/config/examples/:/home/"
      - "/opt/openfhir/config/license.json:/app/openfhir-license.json"
      - "/opt/openfhir/config/bootstrap/:/app/bootstrap/"
      - "/opt/openfhir/config/application.yaml:/application.yaml"

  postgres:
    image: postgres:16
    network_mode: host
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - "/opt/openfhir/config/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql"

init-db.sql

-- Create OpenFHIR database and user
CREATE DATABASE openfhir;
CREATE USER openfhir WITH ENCRYPTED PASSWORD 'openfhir';
GRANT ALL PRIVILEGES ON DATABASE openfhir TO openfhir;

-- Ensure the users can connect and create objects within their databases
ALTER DATABASE openfhir OWNER TO openfhir;

-- Grant privileges to schemas (optional but recommended)
ALTER ROLE openfhir SET search_path TO public;

application.yaml

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/openfhir
    username: openfhir
    password: openfhir

server:
  error:
    include-stacktrace: never
    include-message: always
  port: 8080

openfhir:
  db:
    type: postgres
  license: /app/openfhir-license.json
  bootstrap:
    dir: /app/bootstrap
    recursively-open-directories: true

Example docker compose - mongo

Example docker compose file

services:
  openfhir:
    image: openfhir/openfhir-enterprise:latest
    container_name: openfhir
    ports:
      - "8080:8080"
    volumes:
      - "/home/user/Documents/openFHIR/license.json:/app/openfhir-license.json"
      - "/home/user/Documents/openFHIR/openfhir-bootstrap/:/app/bootstrap/"
    environment:
        SPRING_DATA_MONGODB_URI: "mongodb://openfhir:openfhir@mongodb:27017/openfhir"
        OPENFHIR_DB_TYPE: "mongo"
        OPENFHIR_LICENSE: "/app/openfhir-license.json" # could also be left out, as this is the default
        OPENFHIR_BOOTSTRAP_DIR: "/app/bootstrap/" # could also be left out, as this is the default

  mongodb:
    image: mongo:latest
    container_name: mongodb
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: "admin"
      MONGO_INITDB_ROOT_PASSWORD: "admin"
      MONGO_INITDB_DATABASE: "openfhir"
    volumes:
      - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro

init-mongo.js

// Connect to the admin database to create a user for a specific database
db = db.getSiblingDB('openfhir');

// Create a new user with readWrite access to the 'openfhir' database
db.createUser({
    user: 'openfhir',
    pwd: 'openfhir',
    roles: [
        {
            role: 'readWrite',
            db: 'openfhir'
        }
    ]
});

Startup checks

The engine checks its license and database configuration at startup. When something is wrong it stops and logs an APPLICATION FAILED TO START block that says what is wrong and what to do about it, for example:

***************************
APPLICATION FAILED TO START
***************************

Description:

openFHIR license file not found at '/app/openfhir-license.json'.

Action:

Place your license file at that path or point 'openfhir.license' to where it is (relative paths are resolved against the working directory). To obtain a license, visit https://open-fhir.com or contact us at info@open-fhir.com.

This is reported for:

  • a license that is not configured, cannot be found or read, is not a license file, has been modified or has expired (OPENFHIR_LICENSE)

  • an OPENFHIR_DB_TYPE other than mongo or postgres

  • missing database connection settings: SPRING_DATA_MONGODB_URI for mongo; SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME and SPRING_DATASOURCE_PASSWORD (which may be empty) for postgres

  • a database that cannot be reached, whose host does not resolve, that rejects the credentials or the connection, (MongoDB) whose TLS handshake fails or (PostgreSQL) whose database does not exist. Hosts are named, credentials never are

  • invalid queue configuration, or queue support enabled without the messaging license option (see Queue support)

The full stack trace of the failure is logged at DEBUG level (LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_BOOT_DIAGNOSTICS=DEBUG).

License expiry

The license file is validated again once a day. Its expiry date and options are logged at startup and with every check; from 30 days before expiry this is a WARN. GET /status reports them under license (see RESTful APIs).

To renew, overwrite the file at OPENFHIR_LICENSE with the new license before the old one expires (e.g. cp new-license.json license.json); the daily check picks it up without a restart, including options the new license adds or removes (e.g. terminology). The messaging option is only checked at startup, so queue support is switched on or off by a new license only after a restart.

Note

When the license is mounted as a single file, as in the Docker examples above, the container does not see a file that is replaced rather than overwritten (mv, or an editor that saves by renaming), so overwrite it in place. Alternatively, mount a directory of its own and point OPENFHIR_LICENSE into it, e.g. -v /opt/openfhir/license:/app/license:ro with OPENFHIR_LICENSE=/app/license/openfhir-license.json. Do not mount over /app itself, which holds the engine. A Kubernetes Secret or ConfigMap only reaches a running container when it is mounted as a directory, not with subPath; otherwise restart the engine after renewing.

When the check finds the license no longer valid, it logs what is wrong and what to do about it, and checks again 30 seconds later, so a file that is being overwritten or is briefly unavailable does not stop the engine. If the license is still not valid, the engine shuts down gracefully (in-flight requests, queue consumers and cluster membership are closed cleanly) and exits with code 1. If that does not finish within 60 seconds, the process is halted. With a restart policy, the engine then fails to start again with an APPLICATION FAILED TO START message until a valid license is in place.