Bf
Application Stacks AWS

Blaze FHIR Server 1.11.0 on AWS User Guide

| Product: Blaze FHIR Server 1.11.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Blaze FHIR Server 1.11.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened EHRbase & GIS, Health and Research Platforms AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Blaze is an open source FHIR R4 server (Apache-2.0, github.com/samply/blaze). It stores clinical and research resources such as Patient, Observation, Encounter and Condition, answers the FHIR REST API (create, read, update, delete, history, search with chaining and includes, transactions and batches), and evaluates CQL measures, all in one process with an embedded RocksDB store, so there is no separate database to run. It is a JSON REST API without a browser interface: you use it with curl, a FHIR client library or a tool such as Postman.

The cloudimg image adds:

  • HTTPS from the first boot. nginx serves the FHIR API on port 443 with a certificate generated on your instance's first boot (never baked into the image). Port 80 redirects to HTTPS.
  • A password before anything answers. Blaze ships with no authentication. On this image nginx requires HTTP Basic authentication for every FHIR request, with a random password created for user admin on your instance's first boot and stored only as a bcrypt hash. Nothing but SSH answers until first boot has created it, and the credential is never passed on to Blaze.
  • Blaze 1.11.0, pinned. The upstream samply/blaze 1.11.0 container is pinned by its image digest and never pulled at boot. Version 1.11.0 carries the Jetty 12.1.12 and netty 4.2.17 security fixes that 1.10.1 lacks.
  • Its own data volume. The RocksDB store lives on a dedicated 20 GiB gp3 EBS volume mounted at /var/lib/blaze, which you can grow or snapshot on its own.
  • Blaze on loopback, unprivileged. The container is published on 127.0.0.1:8080 only, runs as the unprivileged blaze account with every Linux capability dropped, and is reached only through nginx.
  • Fixed host and real client addresses. Absolute URLs in Blaze's responses (Bundle.link, fullUrl, Location) always use your configured host name, even if a client forges forwarding headers; requests for a host name the instance does not serve are dropped; cross-origin write requests from other web sites are refused; nginx logs the real client address.
  • Metadata service blocked. The Blaze container, Docker and nginx cannot reach the instance metadata service, so a URL configured into Blaze can never reach it, and the AMI requires IMDSv2.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on for Ubuntu and for Docker's repository.

What is on the image: Blaze 1.11.0 (container samply/blaze, Eclipse Temurin 25), Docker Engine, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH) from your management network and TCP 443 (the FHIR API) and TCP 80 (redirects to HTTPS) from the networks your FHIR clients connect from. Never open port 8080.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Blaze FHIR Server 1.11.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above. First boot gives Blaze a quarter of the instance memory as JVM heap and another quarter as RocksDB block cache, so a larger instance serves larger data sets without any change; Step 9 shows how to adjust both.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=blaze}]'

Step 2: Connect to your instance

Version SSH user
Blaze FHIR Server 1.11.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot takes about a minute: it creates the certificate and the password, sizes the memory settings, starts Blaze against its empty store and only then lets nginx start. Then every unit is active and the Blaze container is up, published on loopback only:

systemctl is-active docker blaze nginx blaze-firstboot
sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
active
active
active
active
NAMES     STATUS          PORTS
blaze     Up 2 minutes    127.0.0.1:8080->8080/tcp

The image runs exactly one container image, pinned by digest:

sudo docker image ls --digests --format '{{.Repository}}@{{.Digest}}'
samply/blaze@sha256:1a60d9ca0d579d8296b65ae3c24fea838730e4267e39aa7676ebf945140be3cb

Step 4: Check what the network can reach

sudo /usr/local/sbin/blaze-port-check.sh
off-box TCP exactly [22 80 443]; Blaze on loopback 8080
BLAZE_PORTS_OK

That is the complete network surface of a new instance: SSH, the HTTPS front door and the HTTP redirect. Blaze itself listens on loopback only.

Step 5: Read your password

First boot writes this instance's FHIR base URL, the user name and its password to a root only file:

sudo cat /root/blaze-credentials.txt
# Blaze FHIR Server 1.11.0 on Ubuntu 24.04 (cloudimg)
# Generated on this instance's first boot. These credentials are unique to THIS instance.

BLAZE_URL=https://<public-dns-name>/
BLAZE_FHIR_BASE=https://<public-dns-name>/fhir
BLAZE_USERNAME=admin
BLAZE_PASSWORD=<BLAZE_PASSWORD>
...

None of these values exists in the image: the password and the certificate are created on your instance's first boot. The host name is your instance's public DNS name (or its IP address when it has none); Step 8 shows how to use your own.

Step 6: Make your first FHIR request

Without the password every FHIR request is refused by nginx before it reaches Blaze, and port 80 only redirects to HTTPS on your configured host name:

curl -sk -o /dev/null -w 'no password: %{http_code}\n' https://127.0.0.1/fhir/metadata
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' http://127.0.0.1/fhir/metadata
no password: 401
port 80: 301 -> https://<public-dns-name>/fhir/metadata

The commands on this page read the password from your credentials file and hand it to curl as a configuration file on its standard input (-K -), so it never appears on a command line or in your shell history. Define this small helper in your SSH session:

blaze_auth() { sudo sed -n 's/^BLAZE_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/blaze-credentials.txt; }
blaze_auth | curl -sk -K - -H 'Accept: application/fhir+json' https://127.0.0.1/fhir/metadata \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["resourceType"], d["software"]["name"], d["software"]["version"], "FHIR", d["fhirVersion"]); print(d["implementation"]["url"])'
CapabilityStatement Blaze 1.11.0 FHIR 4.0.1
https://<public-dns-name>/fhir

The CapabilityStatement lists every resource type, interaction and search parameter the server supports. From your own workstation the same request is curl -k -u admin https://<public-dns-name>/fhir/metadata, which prompts for the password.

Step 7: Create, read and search resources

Create a Patient. Blaze assigns the id and the version:

blaze_auth() { sudo sed -n 's/^BLAZE_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/blaze-credentials.txt; }
blaze_auth | curl -sk -K - -X POST -H 'Content-Type: application/fhir+json' -H 'Prefer: return=representation' \
  --data '{"resourceType":"Patient","identifier":[{"system":"urn:example:mrn","value":"MRN-0001"}],"name":[{"family":"Lovelace","given":["Ada"]}],"gender":"female","birthDate":"1815-12-10"}' \
  https://127.0.0.1/fhir/Patient \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["resourceType"], d["id"], "version", d["meta"]["versionId"], d["name"][0]["family"])'
Patient DIKZ3OG7UZOMNONF version 7 Lovelace

Search for it by its identifier. The fullUrl of every entry is built from your configured host name:

blaze_auth() { sudo sed -n 's/^BLAZE_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/blaze-credentials.txt; }
blaze_auth | curl -sk -K - -H 'Accept: application/fhir+json' 'https://127.0.0.1/fhir/Patient?identifier=urn:example:mrn|MRN-0001' \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); print("total:", d["total"]); [print(e["fullUrl"]) for e in d.get("entry", [])]'
total: 1
https://<public-dns-name>/fhir/Patient/DIKZ3OG7UZOMNONF

To load data in bulk, POST a FHIR transaction or batch Bundle to https://<public-dns-name>/fhir. The Blaze project's separate command line tool blazectl (github.com/samply/blazectl, not installed on the image) uploads directories of bundles, counts resources and downloads search results from your workstation; point it at your FHIR base URL with your admin password.

Step 8: Use your own DNS name and certificate

Create a DNS record for your instance (for example fhir.example.com), then serve Blaze under that name. The helper creates a new certificate for it, pins nginx and the cross-origin guard to it, sets Blaze's base URL and restarts Blaze:

sudo blaze-set-hostname fhir.example.com

The name is remembered in /etc/cloudimg/blaze-hostname. To use a certificate from your own certificate authority, replace /etc/ssl/blaze/blaze.crt and /etc/ssl/blaze/blaze.key (keep the key 0600 root) and run sudo systemctl reload nginx.

Step 9: Storage, memory and the password

The RocksDB store is on its own EBS volume:

findmnt -n -o SOURCE,SIZE,FSTYPE /var/lib/blaze
sudo ls -l /var/lib/blaze
cat /etc/blaze-cloudimg/blaze-tuning.env
/dev/nvme1n1 19.5G ext4
total 20
drwxr-xr-x 2 blaze blaze 4096 Oct 20 15:11 admin-index
drwxr-xr-x 2 blaze blaze 4096 Oct 20 15:11 admin-transaction
drwxr-xr-x 2 blaze blaze 4096 Oct 20 15:11 index
drwxr-xr-x 2 blaze blaze 4096 Oct 20 15:11 resource
drwxr-xr-x 2 blaze blaze 4096 Oct 20 15:11 transaction
# Rendered by first boot from this instance memory (7816 MiB) (cloudimg). Edit and `sudo systemctl restart blaze`.
JAVA_TOOL_OPTIONS=-Xmx1954m
DB_BLOCK_CACHE_SIZE=1954
  • Grow the store: enlarge the volume in the EC2 console, then run sudo resize2fs on the device shown by findmnt. Snapshot the volume to back up your data; stop Blaze (sudo systemctl stop blaze) first for a consistent snapshot.
  • Memory: edit /etc/blaze-cloudimg/blaze-tuning.env (JVM heap and RocksDB block cache in MiB) and run sudo systemctl restart blaze. Other Blaze settings from its documentation can be added to the same file.
  • Password: sudo blaze-set-password creates a new random password and updates /root/blaze-credentials.txt; sudo blaze-set-password --stdin reads one you choose (12 or more characters) from standard input. nginx picks it up on the next request.

Step 10: Metadata access is blocked

Blaze can be configured to call other services (an OpenID Connect provider, an external terminology server). The image drops any container traffic to the instance metadata service in Docker's DOCKER-USER firewall chain, and Docker, Blaze's unit and nginx are denied it by systemd as well:

sudo iptables -S DOCKER-USER
sudo docker exec blaze curl -s -m 5 -o /dev/null -w 'metadata from inside the container: %{http_code}\n' http://169.254.169.254/latest/meta-data/ || true
-N DOCKER-USER
-A DOCKER-USER -d 168.63.129.16/32 -j DROP
-A DOCKER-USER -d 169.254.169.254/32 -j DROP
metadata from inside the container: 000

000 means the connection never completed. The instance itself still uses IMDSv2 normally (first boot reads its host name from it).

Step 11: Updates

  • Ubuntu and Docker receive security updates automatically through unattended-upgrades.
  • Blaze is the pinned 1.11.0 container. cloudimg publishes a new version of this listing when a new Blaze release is needed; to move an existing instance to it, snapshot the data volume, launch the new version and attach the volume (or restore its snapshot) at /var/lib/blaze.

Security recommendations

  • Open port 443 only to the networks your FHIR clients use, and port 22 only to your management network.
  • Use your own DNS name and a certificate your clients trust (Step 8) before connecting production systems.
  • The HTTP Basic password is a single shared credential for the whole server. For per-client access control, enable Blaze's OpenID Connect authentication (see the Blaze documentation) behind the same nginx front door.
  • This image is infrastructure. If you store protected health information you are responsible for the controls it needs (encryption, access control, audit, backups and the regulations that apply to you, such as HIPAA or GDPR); the image is not a certified compliant product.

Support and licensing

cloudimg provides 24/7 support for this image at support@cloudimg.co.uk.

Blaze is licensed under the Apache License 2.0; the licence and this build's provenance are in /usr/share/doc/blaze-cloudimg/. Blaze is developed by the samply project; this image is provided by cloudimg and is not affiliated with or endorsed by the Blaze maintainers.