Application Infrastructure Azure

Open Integration Engine on Ubuntu 24.04 on Azure User Guide

| Product: Open Integration Engine on Ubuntu 24.04 LTS on Azure

Open Integration Engine on Ubuntu 24.04 LTS on Azure

This guide walks you through deploying the cloudimg Open Integration Engine image on Microsoft Azure, signing in with the admin password generated uniquely for your VM, and pushing your first message through a channel.

Open Integration Engine (OIE) is a community governed, MPL-2.0 licensed integration engine for healthcare interoperability. You build channels that receive, filter, transform and route messages between systems, with first class support for HL7 v2, FHIR, DICOM, X12, XML, JSON and delimited formats, plus JavaScript transformers and connectors for HTTP, TCP/MLLP, files, databases and JMS.

This image ships version 4.6.0.

What the image gives you

Engine Open Integration Engine 4.6.0 on OpenJDK 17
Base OS Ubuntu 24.04 LTS, fully patched at build time
Web edge nginx on port 443 (TLS) in front of the engine; port 80 serves a health endpoint and redirects to HTTPS
Admin credential Generated per VM on first boot. No shared or default password ships in the image
Data Embedded Derby database, keystore and configuration on a dedicated 20 GiB Azure data disk
Self test oie-engine-selftest.sh proves the engine really processes a message
Recommended size Standard_B2s (2 vCPU / 4 GiB) for evaluation and light workloads

Compliance note. This image is infrastructure. You remain responsible for every HIPAA, PHI and GDPR control that applies to your deployment, including your own domain with a trusted TLS certificate, network isolation, access control, audit logging and encryption at rest. Nothing here is certified compliant.


1. Launch the VM

From the Azure portal

  1. Open the offer in the Azure Marketplace and choose Get It Now, then Create.
  2. Pick your subscription, resource group and region.
  3. Choose a VM size. Standard_B2s is enough to evaluate the engine and run light channel traffic; scale up for production throughput.
  4. Set the administrative username to azureuser and supply your SSH public key.
  5. On the Networking tab allow inbound 22 (SSH) and 443 (HTTPS). Port 80 is optional and only serves a health endpoint plus a redirect to HTTPS.
  6. Create the VM.

From the Azure CLI

These commands run on your own workstation, not on the VM:

az vm create \
  --resource-group my-resource-group \
  --name my-oie-vm \
  --image <offer-urn-from-the-marketplace-listing> \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

# Then open only the ports you need, restricted to the networks that should reach them.
az vm open-port --resource-group my-resource-group --name my-oie-vm \
  --port 443 --priority 1010 --source-address-prefixes <your-mgmt-cidr>
az vm open-port --resource-group my-resource-group --name my-oie-vm \
  --port 22  --priority 1020 --source-address-prefixes <your-mgmt-cidr>

First boot takes about a minute: the engine creates a fresh database, rotates the admin password to a value unique to your VM, generates a TLS certificate for this VM, and only then starts nginx.


2. Retrieve your per-VM admin password

The image ships no usable credential. The password is generated on your VM at first boot and written to a root-only file.

ssh azureuser@<vm-ip>
sudo cat /root/oie-credentials.txt
# Open Integration Engine - generated on first boot by oie-firstboot.service
# These credentials are unique to this VM. Store them somewhere safe.

OIE_URL=https://<vm-ip>/
OIE_API_BASE=https://<vm-ip>/api
OIE_USERNAME=admin
OIE_PASSWORD=<OIE_PASSWORD>

Copy the password into your password manager, then confirm the services are healthy:

systemctl status oie nginx --no-pager --lines=5

Open Integration Engine and nginx running as active systemd services on Ubuntu 24.04

You can see exactly what first boot did, including the credential rotation and the proof that the old default login no longer works:

sudo journalctl -u oie-firstboot.service --no-pager | tail -14

First boot log showing the admin password rotated to a per-VM value, the default login rejected, and the per-VM password accepted


3. Open the web interface

Browse to https://<vm-public-ip>/.

The appliance uses a self-signed certificate generated for your VM, so your browser will warn you the first time. Accept the warning for evaluation, and see section 8 before you go to production.

The Open Integration Engine service landing page served over TLS, offering the Administrator Launcher URL and a link to the Client API

This landing page confirms the backend is running and gives you the URL to point an Administrator Launcher at. Two things are worth understanding here:

  • Channel authoring happens in the Administrator, a desktop application you download separately from the project. Point it at the URL shown on this page and sign in as admin with your per-VM password.
  • Everything the Administrator does is also available over the REST API, which is fully browsable on this VM. That is the path this guide uses, because it works from any machine with curl and needs nothing installed.

4. Explore the Client API

Click Go to API, or browse to https://<vm-public-ip>/api/.

The Open Integration Engine Client API explorer, signed in as admin, listing the available API groups

Expanding Channels shows the operations you use to create, deploy, start, stop and inspect channels.

The Channels group expanded in the API explorer, showing the endpoints for retrieving, creating and removing channels

One API rule to remember. The engine ships with server.api.require-requested-with = true, so every REST call must carry an X-Requested-With header. The API explorer adds it for you. From curl you must add it yourself, as shown below. Without it the engine replies 400 All requests must have 'X-Requested-With' header.

Sign in from the command line and keep the session cookie:

PW=$(sudo grep '^OIE_PASSWORD=' /root/oie-credentials.txt | cut -d= -f2-)
curl -sk -c /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
  --data-urlencode 'username=admin' \
  --data-urlencode "password=${PW}" \
  https://127.0.0.1/api/users/_login

A successful sign in returns a LoginStatus of SUCCESS. Check the engine version the same way:

curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
  https://127.0.0.1/api/server/version
4.6.0

5. Prove the engine works, in one command

A web page returning 200 tells you a web server is up. It does not tell you the engine can actually move a message. The image ships a self test that answers the real question: it deploys a temporary channel, pushes an HL7 v2 message through it, checks the engine's own statistics, and removes the channel again.

sudo /usr/local/sbin/oie-engine-selftest.sh

The engine self test deploying a channel, pushing an HL7 message through it, and reporting received=1 sent=1 error=0

received=1 sent=1 error=0 means the message travelled through the source connector, the transformer and the destination connector. Run this any time you want to confirm the engine is healthy, including after you change configuration.


6. Deploy your first channel

The image ships the self test's channel definition at /usr/share/cloudimg/oie/example-channel.xml so you can run this section verbatim. It is a minimal Channel Reader source feeding a JavaScript Writer destination.

Give it an id and a name of your own:

CHANNEL_ID=$(cat /proc/sys/kernel/random/uuid)
echo "${CHANNEL_ID}" | sudo tee /tmp/oie-channel-id >/dev/null
sudo sed -e "s/CLOUDIMG_CHANNEL_ID/${CHANNEL_ID}/" \
         -e "s/CLOUDIMG_CHANNEL_NAME/my-first-channel/" \
         /usr/share/cloudimg/oie/example-channel.xml | sudo tee /tmp/my-channel.xml >/dev/null
echo "prepared channel ${CHANNEL_ID}"

Create it:

curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
  -H 'Content-Type: application/xml' \
  --data-binary @/tmp/my-channel.xml \
  -o /dev/null -w 'create: HTTP %{http_code}\n' \
  https://127.0.0.1/api/channels

Deploy it:

CHANNEL_ID=$(cat /tmp/oie-channel-id)
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
  -H 'Content-Type: application/xml' \
  --data "<set><string>${CHANNEL_ID}</string></set>" \
  -o /dev/null -w 'deploy: HTTP %{http_code}\n' \
  "https://127.0.0.1/api/channels/_deploy?returnErrors=true"

Confirm it reached STARTED:

CHANNEL_ID=$(cat /tmp/oie-channel-id)
for i in $(seq 1 30); do
  STATE=$(curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
    "https://127.0.0.1/api/channels/${CHANNEL_ID}/status" | grep -oE '<state>[A-Z]+</state>')
  echo "${STATE}"
  case "${STATE}" in *STARTED*) break;; esac
  sleep 2
done

Push an HL7 v2 message through it:

CHANNEL_ID=$(cat /tmp/oie-channel-id)
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
  -H 'Content-Type: text/plain' \
  --data-binary 'MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20260101000000||ADT^A01|MSG00001|P|2.5' \
  -w '\nsubmit: HTTP %{http_code}\n' \
  "https://127.0.0.1/api/channels/${CHANNEL_ID}/messages?destinationMetaDataId=1"

Do not omit destinationMetaDataId. It names the destination connectors the message should be routed to. Without it the engine accepts the message and runs the source connector only: statistics show received=1 and sent=0 forever, and your destinations never fire. This is the single most common surprise when driving the engine from the REST API.

Check the statistics. received and sent should both be 1:

CHANNEL_ID=$(cat /tmp/oie-channel-id)
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
  "https://127.0.0.1/api/channels/${CHANNEL_ID}/statistics"

The API explorer executing the channel statistics call and returning received 1, sent 1 and error 0 for a channel that processed a message

When you are finished with the example, undeploy and remove it:

CHANNEL_ID=$(cat /tmp/oie-channel-id)
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
  -H 'Content-Type: application/xml' \
  --data "<set><string>${CHANNEL_ID}</string></set>" \
  -o /dev/null -w 'undeploy: HTTP %{http_code}\n' \
  https://127.0.0.1/api/channels/_undeploy
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
  -X DELETE -o /dev/null -w 'delete: HTTP %{http_code}\n' \
  "https://127.0.0.1/api/channels/${CHANNEL_ID}"

To build real channels, export a definition from the Administrator and create it the same way.


7. Opening ports for your own channel listeners

The image opens only what the appliance itself needs: 22, 80 and 443.

Listener ports belong to the channels you create. An MLLP listener on 6661, an HTTP listener on 8081 or a TCP listener on any port will bind on the VM as soon as you deploy the channel, but Azure will not admit traffic to it until you open that port too:

az vm open-port --resource-group my-resource-group --name my-oie-vm \
  --port 6661 --priority 1030 --source-address-prefixes <your-mgmt-cidr>

Open only the ports you actually use, and prefer restricting the source address range on the NSG rule to the systems that are meant to talk to the engine.


8. Security posture and hardening

The image is secure by default in three specific ways.

No known credential ships in the image. The engine's database is wiped before the image is captured, so the captured image contains no account at all. Your VM creates the database fresh on first boot and rotates the admin password to a value unique to your VM before the public web edge is started.

The engine is not directly exposed. Its admin and REST listeners bind to 127.0.0.1 only. nginx on ports 80 and 443 is the sole public edge.

sudo ss -tlnp | grep -E ':8443|:8080|:443|:80 '

The engine listening only on loopback with nginx as the public edge, the default admin login returning FAIL, and no swap configured

The old default login is provably dead. You can check this yourself at any time:

curl -sk -H 'X-Requested-With: XMLHttpRequest' \
  --data-urlencode 'username=admin' --data-urlencode 'password=admin' \
  https://127.0.0.1/api/users/_login

This returns <status>FAIL</status>. If it ever returns SUCCESS, stop and rebuild the VM.

Before production

  1. Replace the TLS certificate. Point a DNS name at the VM and install a trusted certificate:

bash sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx sudo certbot --nginx -d oie.example.com

  1. Restrict access. Limit inbound 443 to the networks that need the Administrator or API, using an NSG rule or a private endpoint.
  2. Change the admin password to one of your own and create named per-person accounts rather than sharing admin.
  3. Back up the data disk. Everything stateful lives on it, so an Azure snapshot schedule against that disk captures your channels and message history.

9. Where everything lives

Path Contents
/opt/oie Engine installation, launcher, extensions and logs
/opt/oie/conf/mirth.properties Engine configuration, including the loopback bind and data directory
/opt/oie/conf/custom.vmoptions JVM options, including the heap size
/var/lib/oie/appdata Data disk. Derby database, keystore, configuration map and temporary data
/root/oie-credentials.txt Your per-VM admin credentials, root readable only
/usr/local/sbin/oie-engine-selftest.sh The engine self test

Service management:

sudo systemctl status oie
sudo systemctl restart oie
sudo journalctl -u oie -f

Tuning the JVM heap

The image sets a 1 GiB maximum heap, which suits Standard_B2s. If you move to a larger VM and drive real message volume, raise it:

sudo sed -i 's/^-Xmx1024m/-Xmx3072m/' /opt/oie/conf/custom.vmoptions
sudo systemctl restart oie

Leave at least a gigabyte for the operating system, nginx and the database cache. Never add a swap file to compensate for an undersized VM; move to a larger size instead.


10. Troubleshooting

The web page does not load. Confirm nginx is running and port 443 is open in the NSG:

sudo systemctl status nginx --no-pager
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz

/healthz returns 200 from the VM itself whenever the edge is healthy.

The credentials file still shows the placeholder. First boot has not finished. Watch it:

sudo journalctl -u oie-firstboot.service -f

The engine's first start creates the database and can take up to a couple of minutes.

A REST call returns 400. You omitted the X-Requested-With header. See section 4.

A channel receives messages but never sends them. You omitted destinationMetaDataId when submitting the message. See section 6.

The engine will not start after a configuration change. Check the log:

sudo journalctl -u oie --no-pager | tail -50
sudo tail -50 /opt/oie/logs/mirth.log

Support

This image is maintained by cloudimg with 24/7 support for deployment, upgrades, TLS setup and channel configuration questions. Contact support@cloudimg.co.uk.

Open Integration Engine itself is open source under the Mozilla Public License 2.0. Project documentation, the Administrator Launcher downloads and the community are at openintegrationengine.org.