Applications Azure

EHRbase on Ubuntu 24.04 on Azure User Guide

| Product: EHRbase on Ubuntu 24.04 LTS on Azure

Overview

EHRbase is an open-source openEHR Clinical Data Repository: a standards-based backend for storing and querying structured electronic health record data. It implements the openEHR Reference Model, stores clinical compositions against uploaded operational templates, and exposes the official openEHR REST API together with the Archetype Query Language (AQL) query endpoint, so clinical applications and integration engines write and read vendor-neutral, semantically rich health data over HTTP and JSON.

The cloudimg image installs EHRbase 2.34 under a dedicated non-root ehrbase service user on the Eclipse Temurin Java 25 runtime it requires, backed by a local standard PostgreSQL 16 database that has already been bootstrapped with the two roles, the ehr and ext schemas and the uuid-ossp extension EHRbase expects. EHRbase manages its own database schema with Flyway and applies all migrations automatically on first start. Its embedded server serves the openEHR REST API over HTTP on port 8080 under the /ehrbase context path. EHRbase is a headless API server with no rich web UI — it is designed to sit behind your own applications, reverse proxy and identity provider.

EHRbase ships with documented default Basic-auth credentials. Because a clinical data repository holds sensitive health data, this image is secure by default: on the first boot of every deployed VM a one-shot service rotates both the built-in ehrbase-admin and ehrbase-user passwords to unique per-VM values, rotates the two PostgreSQL role passwords in lockstep, disables EHRbase's insecure default profile, and recreates the database empty — all before the API accepts traffic. The per-VM passwords are written to /root/ehrbase-credentials.txt with mode 0600, so no two VMs share credentials. Backed by 24/7 cloudimg support.

What is included:

  • EHRbase 2.34 (/usr/local/ehrbase/ehrbase.jar) run as the non-root ehrbase user on the Eclipse Temurin JRE 25 runtime
  • The official openEHR REST API served over HTTP on port 8080 under the /ehrbase context path
  • A local standard PostgreSQL 16 as EHRbase's backing database, pre-bootstrapped with the ehrbase and ehrbase_restricted roles, the ehr/ext schemas and the uuid-ossp extension
  • Automatic Flyway schema creation and migration on first start
  • Secure by default: the ehrbase-admin and ehrbase-user Basic-auth passwords and both PostgreSQL role passwords are rotated per VM on first boot; the insecure default profile is disabled and the default credentials are neutralised
  • Per-VM credentials written to a root-only file
  • ehrbase.service and postgresql.service as systemd units, enabled and active
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2ms (2 vCPU / 8 GiB RAM) is the recommended starting point — EHRbase runs a Java virtual machine (embedded Tomcat, Hibernate and Flyway) alongside a local PostgreSQL server, so it benefits from memory headroom. NSG inbound: allow 22/tcp from your management network and 8080/tcp from the networks the applications that consume the openEHR API will connect from. EHRbase serves plain HTTP on port 8080 — for anything beyond a private network, put it behind a TLS-terminating reverse proxy (see below) and expose that instead.

Step 1 — Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for EHRbase by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22). Then Review + createCreate.

First-boot initialisation takes approximately one to two minutes after the VM starts — EHRbase runs its Flyway database migrations, rotates the Basic-auth and database passwords, and starts the embedded API server.

Step 2 — Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name ehrbase \
  --image <marketplace-image-urn> \
  --size Standard_B2ms \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard

az vm open-port --resource-group <your-rg> --name ehrbase --port 8080 --priority 1010

Step 3 — Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 — Confirm the services are running

systemctl is-active postgresql ehrbase
PASS=$(sudo grep '^EHRBASE_USER_PASSWORD=' /root/ehrbase-credentials.txt | cut -d= -f2-)
curl -s -u "ehrbase-user:$PASS" -H 'Accept: application/json' http://127.0.0.1:8080/ehrbase/rest/status

Both services report active, and the authenticated /rest/status endpoint returns JSON reporting the EHRbase, openEHR, Java and PostgreSQL versions, confirming the openEHR REST API is up:

active
active
{"ehrbase_version":"2.34.0","openehr_sdk_version":"2.34.0","archie_version":"3.13.0","jvm_version":"Eclipse Adoptium 25.0.3+9-LTS","os_version":"Linux amd64 6.17.0-1020-azure","postgres_version":"PostgreSQL 16.14 (Ubuntu 16.14-0ubuntu0.24.04.1) ..."}

On the very first boot allow a minute or two for the database migration and the API server to start before the endpoint responds.

EHRbase and PostgreSQL services active, the openEHR REST status endpoint reporting versions, and the embedded server listening on port 8080

Step 5 — Retrieve your credentials

EHRbase's built-in ehrbase-admin and ehrbase-user Basic-auth passwords are rotated to values unique to your VM on first boot and written to a root-only file:

sudo cat /root/ehrbase-credentials.txt
EHRBASE_ADMIN_USERNAME=ehrbase-admin
EHRBASE_ADMIN_PASSWORD=<generated-per-VM>
EHRBASE_USER_USERNAME=ehrbase-user
EHRBASE_USER_PASSWORD=<generated-per-VM>
EHRBASE_URL=http://<vm-address>:8080/ehrbase/

The ehrbase-user account is for openEHR data access (creating EHRs, storing and querying compositions); the ehrbase-admin account is for privileged operations. Both authenticate with HTTP Basic auth.

The per-VM Basic-auth credentials generated at first boot, written to a root-only file

Step 6 — Secure by default

Because EHRbase holds sensitive health data, the image ships with no usable default credential. You can confirm the rotation from the shell: the publicly documented default password SuperSecretPassword is rejected, while the per-VM password from the credentials file authenticates.

PASS=$(sudo grep '^EHRBASE_USER_PASSWORD=' /root/ehrbase-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'default password: HTTP %{http_code}\n' -X POST -u 'ehrbase-user:SuperSecretPassword' \
  -H 'Content-Type: application/json' http://127.0.0.1:8080/ehrbase/rest/openehr/v1/ehr
curl -s -o /dev/null -w 'per-VM password: HTTP %{http_code}\n' -X POST -u "ehrbase-user:$PASS" \
  -H 'Content-Type: application/json' http://127.0.0.1:8080/ehrbase/rest/openehr/v1/ehr
default password: HTTP 401
per-VM password: HTTP 201

The 401 for the built-in default and 201 for the per-VM password prove the first-boot rotation. The two PostgreSQL role passwords are rotated in the same way on first boot, and EHRbase's insecure default profile (which would disable authentication) is switched off.

Secure by default — the documented default password is rejected and the per-VM credential authenticates

Step 7 — The openEHR REST API

Everything in EHRbase is driven through the standard openEHR REST API. Authenticate with HTTP Basic auth using the ehrbase-user credential, then create an EHR — the fundamental container for a subject's health record:

PASS=$(sudo grep '^EHRBASE_USER_PASSWORD=' /root/ehrbase-credentials.txt | cut -d= -f2-)
curl -s -D - -o /dev/null -X POST -u "ehrbase-user:$PASS" \
  -H 'Content-Type: application/json' -H 'Prefer: return=representation' \
  http://127.0.0.1:8080/ehrbase/rest/openehr/v1/ehr | grep -iE 'HTTP/|ETag|Location'

EHRbase creates a new EHR and returns 201 Created with the new ehr_id in the Location and ETag headers:

HTTP/1.1 201
Location: http://127.0.0.1:8080/ehrbase/rest/openehr/v1/ehr/869e9ad6-0d7b-44c2-915d-f68393ea5166
ETag: "869e9ad6-0d7b-44c2-915d-f68393ea5166"

That ehr_id is the handle you use to store and retrieve compositions for the subject. The full openEHR REST API — EHRs, compositions, contributions, templates, directories and the AQL query endpoint — is served under http://<vm-address>:8080/ehrbase/rest/openehr/v1/.

Creating an EHR through the openEHR REST API — HTTP 201 with the new ehr_id in the Location and ETag headers

Step 8 — Upload an operational template

openEHR stores clinical data as compositions that conform to an operational template (an .opt file exported from a modelling tool such as the openEHR Archetype Designer). Upload a template once, then post compositions against it. Templates are managed under the openEHR definition/template/adl1.4 endpoint:

PASS=$(sudo grep '^EHRBASE_USER_PASSWORD=' /root/ehrbase-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'upload: HTTP %{http_code}\n' -X POST -u "ehrbase-user:$PASS" \
  -H 'Content-Type: application/xml' --data-binary @your-template.opt \
  http://127.0.0.1:8080/ehrbase/rest/openehr/v1/definition/template/adl1.4

A 201 response means the template is registered; you can then POST compositions that reference its template id to .../ehr/<ehr_id>/composition. List the templates already loaded with a GET on the same endpoint.

Step 9 — Query with AQL

The Archetype Query Language endpoint lets you query across EHRs and compositions in a template-agnostic way. Count the EHRs in the repository:

PASS=$(sudo grep '^EHRBASE_USER_PASSWORD=' /root/ehrbase-credentials.txt | cut -d= -f2-)
curl -s -u "ehrbase-user:$PASS" -H 'Accept: application/json' \
  --data-urlencode 'q=SELECT COUNT(e/ehr_id/value) FROM EHR e' -G \
  http://127.0.0.1:8080/ehrbase/rest/openehr/v1/query/aql

The response is an openEHR result set in JSON, with the count in the rows array:

{"meta":{"_type":"RESULTSET", ...},"q":"SELECT COUNT(e/ehr_id/value) FROM EHR e",
 "columns":[{"name":"#0"}],"rows":[[0]]}

On a fresh appliance the count is 0; each EHR you create increments it. AQL is the primary way applications and analytics pipelines read structured data back out of EHRbase — you query fields by their openEHR path (here e/ehr_id/value) rather than the full object.

Putting EHRbase behind TLS

The image serves the openEHR API over plain HTTP on port 8080, which is appropriate for a private network or a co-located application. For anything exposed more widely, terminate TLS in front of EHRbase with your own reverse proxy (nginx, Caddy, Azure Application Gateway) and forward to http://127.0.0.1:8080/ehrbase/. Front it with your identity provider as well if you need OAuth2/OIDC instead of Basic auth — EHRbase supports an OAUTH security mode you can switch to in /etc/ehrbase/ehrbase.env.

Backup and maintenance

EHRbase keeps all of its clinical data — EHRs, compositions, templates and the AQL indexes — in its PostgreSQL backing database. Back it up regularly:

sudo -u postgres pg_dump ehrbase > /tmp/ehrbase-db-$(date +%F).sql

Ship the dump to Azure Blob Storage or another object store, or snapshot the OS disk in Azure for a coordinated point-in-time backup. Keep the OS patched with sudo apt update && sudo apt upgrade. To upgrade EHRbase itself, follow the vendor upgrade notes for your target version, always taking a database backup first; EHRbase migrates its schema automatically on the next start.

Support

This image is backed by 24/7 cloudimg support. Contact us by email and chat for help with deployment, openEHR template and archetype modelling, AQL query design, reverse-proxy and TLS termination, identity-provider integration and PostgreSQL administration. Contact support@cloudimg.co.uk.

For general EHRbase questions consult the documentation at https://ehrbase.readthedocs.io and https://github.com/ehrbase/ehrbase. EHRbase is an open-source project; openEHR is a trademark of the openEHR Foundation. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.