Deployment guide

Dicoogle

Overview

Dicoogle is an extensible, open source PACS archive developed by UA.PT Bioinformatics and BMD Software. Instead of a fixed centralised database, it indexes DICOM metadata, including private attribute tags, through a plugin-based index and query engine. You can search an imaging archive with free-text and field-qualified queries, rather than only the handful of fields a traditional PACS exposes. It ships a web UI for search and administration, a REST API, and standard DIMSE services so that modalities and other PACS can store to it and query it.

What you get:

  • The pinned official Dicoogle 3.6.0 release (OpenJDK 17) under systemd, installed from the checksummed release and running as the dicoogle systemd service, locked down as an appliance
  • The two plugins that make it a complete archive, the Lucene index/query provider and the file storage provider, pre-wired so that a received DICOM object is stored and indexed
  • The web UI and REST API on :80, fronted by nginx with Dicoogle bound to loopback only
  • A per-VM administrator password, generated on first boot and used with Dicoogle's own account system. The upstream default dicoogle/dicoogle account never exists on your VM
  • A per-VM DICOM calling-AE allow-list, so the storage and query/retrieve services reject associations from unknown senders. This replaces upstream's accept-from-anyone default
  • A dedicated Azure data disk at /var/lib/dicoogle holding the archive, the Lucene index and the configuration
  • dicoogle.service and nginx.service as systemd units, enabled and active
  • An unauthenticated /healthz endpoint on :80 for Azure Load Balancer health probes
  • A synthetic self-test study stored at first boot, proving the archive stores, indexes and retrieves
  • 24/7 SecureAnyCloud support

Important: compliance is your responsibility

This image is not a medical device and is not HIPAA, GDPR or IEC 62304 compliant out of the box. It is general-purpose self-hosted imaging software packaged for convenience.

Running it in a clinical or research setting with real patient data makes you the operator of that system, and the compliance posture is yours to establish. At minimum, terminate TLS with your own certificate, restrict network access to known clinical subnets, configure audit logging and retention to your regulator's requirements, arrange backup and disaster recovery for the data volume, confirm data residency, and complete any processing agreements your jurisdiction requires. Do not place real patient data on this VM until you have done so.

The only imaging object this image ships with is a synthetic phantom (an 8x8 greyscale test pattern under the patient name SecureAnyCloud^PHANTOM), generated on your VM at first boot to prove the archive works. It contains no real or derived patient data, and you can delete it at any time.

Important: the DICOM listener is closed by default

Upstream Dicoogle ships its allowed-aetitles list empty, and an empty list means accept an association from any calling AE title. Combined with services that autostart on all interfaces, a stock deployment is an open PACS that will accept unauthenticated image stores from anyone who can reach the port. This image never does that:

  • A per-VM calling-AE allow-list. First boot generates a random calling AE title (for example SAC-a1b2c3d) and writes it into the allow-list, so the permit-all path is never active. Any association whose calling AE title is not on the list is aborted.
  • The DIMSE ports are not opened by the image's network security group. Ports 6666 (storage) and 1045 (query/retrieve) are deliberately absent from the image's inbound rules. You open them yourself, from known source ranges, once you have added your modality's AE title.

Both layers are verified on your VM at first boot before the archive is declared ready.

Prerequisites

  • An active Azure subscription, an SSH key pair, and a VNet and subnet in the target region.
  • Standard_B2s (2 vCPU / 4 GiB RAM) is the recommended starting size and is what the appliance is tuned for. The JVM heap is pinned to 1400 MB, which leaves headroom for the OS and the page cache that the Lucene index reads through. Size up the VM and the heap (JAVA_OPTS in /etc/systemd/system/dicoogle.service) for larger archives.
  • NSG inbound rules: allow 22/tcp from your management network and 80/tcp for the web UI. Add 443/tcp if you terminate TLS. Add 6666/tcp and 1045/tcp only from your modality or PACS subnets, once the AE allow-list is configured.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure portal, select Create a resource, search the Marketplace for Dicoogle by SecureAnyCloud, 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) and HTTP (80).

Review the dedicated data disk on the Disks tab. Then select Review + create, then Create.

Step 2 - Deploy from the Azure CLI

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:dicoogle-ubuntu-24-04:default:latest
az vm create \
  --resource-group <your-rg> \
  --name dicoogle \
  --image sac1647283583153:dicoogle-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --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 dicoogle --port 80 --priority 1010

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

systemctl is-active dicoogle.service nginx.service

Both report active. Dicoogle serves its web UI and REST API on the loopback address 127.0.0.1:18080 only, and nginx fronts port 80.

The DIMSE storage service listens on 6666 and query/retrieve on 1045. The Dicoogle service itself owns both ports, and the calling-AE allow-list gates both.

Step 5 - Retrieve your per-VM credentials

Every VM generates its own web password and its own DICOM calling-AE title on first boot. Read them over SSH:

sudo cat /root/dicoogle-credentials.txt

You will see DICOOGLE_URL, DICOOGLE_USERNAME (dicoogle), DICOOGLE_PASSWORD, and DICOOGLE_STORE_AETITLE, which is the calling AE title your modality must present. Store them in your password manager.

Step 6 - Confirm the appliance is secure by default

The upstream default account is not merely renamed. It is never created. Confirm it for yourself:

curl -s -o /dev/null -w 'upstream default -> HTTP %{http_code}\n' -X POST 'http://127.0.0.1/login?username=dicoogle&password=dicoogle'
curl -s -o /dev/null -w 'unauthenticated -> HTTP %{http_code}\n' 'http://127.0.0.1/search?query=SAC'

Both return 401. The password stored for the dicoogle account is a bcrypt hash of the random value generated on your VM.

Step 7 - Sign in to the web UI

Open http://<vm-public-ip>/ in a browser and sign in as dicoogle with the password from Step 5.

After signing in you land on the Search page, with Management, Indexer and About in the sidebar.

Step 8 - Search the archive

Dicoogle's search box accepts free text and Lucene field-qualified queries. Field-qualified queries are the reliable form for identifiers that contain hyphens, because free text is tokenised. To find the synthetic self-test study that first boot stored, search:

PatientID:SAC-PHANTOM-001

The results appear as a drill-down from Patient to Study to Series to Image. Select a row to descend a level.

Descending to the Study level shows each stored study with its date, description and modality.

Useful query forms:

PatientID:SAC-PHANTOM-001 PatientName:SAC* Modality:CT StudyDate:[20260101 TO 20261231] Modality:CT AND StudyDate:[20260101 TO 20260131]

The same queries are available over the REST API. Sign in to obtain a token, then query:

PASS=$(sudo grep '^DICOOGLE_PASSWORD=' /root/dicoogle-credentials.txt | cut -d= -f2-)
TOKEN=$(curl -s -X POST "http://127.0.0.1/login?username=dicoogle&password=$PASS" | sed -n 's/.*"token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
curl -s -H "Authorization: $TOKEN" 'http://127.0.0.1/search?query=PatientID:SAC-PHANTOM-001&provider=lucene' | head -c 600 echo

Step 9 - Review the DICOM services

In the web UI choose Management, then the Services and Plugins tab. You will see the server AE title (DICOOGLE-STORAGE), the Storage service on port 6666 and the Query Retrieve service on port 1045, both running, plus the loaded index, query and storage plugins.

Step 10 - Confirm the DICOM allow-list is enforced

The allow-list is what keeps the archive from accepting images from anyone. Confirm the configured value, then prove that an unknown calling AE title is refused:

sudo grep 'allowed-aetitles' /var/lib/dicoogle/confs/server.xml

Generate a fresh synthetic object and attempt to store it with a calling AE title that is not on the list:

SOP=$(sudo /usr/local/sbin/dicoogle-make-phantom.sh /tmp/guide-test.dcm) storescu -aet UNKNOWN-MODALITY -aec DICOOGLE-STORAGE 127.0.0.1 6666 /tmp/guide-test.dcm || echo "association refused, as expected"

The association is aborted. Now store the same object using this VM's allowed calling AE title:

AET=$(sudo grep '^DICOOGLE_STORE_AETITLE=' /root/dicoogle-credentials.txt | cut -d= -f2-) storescu -aet "$AET" -aec DICOOGLE-STORAGE 127.0.0.1 6666 /tmp/guide-test.dcm && echo "C-STORE accepted"

Step 11 - Confirm the full store, index and retrieve round trip

The object you just stored is indexed within a few seconds and becomes searchable. Query it back by its SOP Instance UID:

PASS=$(sudo grep '^DICOOGLE_PASSWORD=' /root/dicoogle-credentials.txt | cut -d= -f2-)
TOKEN=$(curl -s -X POST "http://127.0.0.1/login?username=dicoogle&password=$PASS" | sed -n 's/.*"token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
UID_=$(sudo grep '^DICOOGLE_SELFTEST_SOP_UID=' /root/dicoogle-credentials.txt | cut -d= -f2-)
curl -s -H "Authorization: $TOKEN" "http://127.0.0.1/search?query=SOPInstanceUID:%22$UID_%22&provider=lucene" | head -c 700 echo

The response carries numResults and the indexed metadata for the stored instance, including the file URI on the archive volume.

Step 12 - Connect a real modality or PACS

To let a modality, workstation or another PACS store into this archive:

  • Add its calling AE title to the allow-list. Edit /var/lib/dicoogle/confs/server.xml and add another <allowed-aetitles> element inside <dicom-services> for each sender, then restart Dicoogle with sudo systemctl restart dicoogle. For example, the list holds <allowed-aetitles>SAC-a1b2c3d</allowed-aetitles> and <allowed-aetitles>CT-SCANNER-01</allowed-aetitles>. Keep the list explicit, because removing every entry restores upstream's accept-from-anyone behaviour.
  • Open the DIMSE port to that sender only. In the Azure portal add an inbound NSG rule for 6666/tcp (and 1045/tcp if the sender performs C-FIND/C-MOVE) with the source restricted to the modality's address or subnet - never Any.
  • Point the modality at this VM, using the server AE title DICOOGLE-STORAGE, the VM's address, and port 6666.

The archive stores objects under /var/lib/dicoogle/storage in a Modality/Year/Month/Day hierarchy and indexes their metadata automatically.

Step 13 - Inspect the archive volume

df -h /var/lib/dicoogle | tail -2 sudo find /var/lib/dicoogle/storage -type f -name '*.dcm' | head -5

The dedicated data disk holds the stored objects, the Lucene index, the configuration and the users file, so you can grow, snapshot and back it up independently of the OS disk.

Removing the synthetic self-test study

The phantom exists only to prove the archive works. Remove it whenever you like. In the web UI, go to Management, then Indexer, and unindex it. Or remove the stored files and re-index:

sudo rm -rf /var/lib/dicoogle/storage/UN_IN
sudo systemctl restart dicoogle

Production hardening

  • Terminate TLS. Dicoogle serves plain HTTP behind nginx. Add your own domain and certificate to the nginx server block before exposing the UI beyond a trusted network.
  • Restrict the web UI. Limit 80/tcp and 443/tcp to the networks that need it.
  • Keep the AE allow-list tight and never leave it empty.
  • Back up /var/lib/dicoogle. It holds the archive, the index and the configuration.
  • Rotate the web password from the Management UI, and keep /root/dicoogle-credentials.txt restricted to root.
  • Patch regularly. Unattended security upgrades are enabled. Reboot for kernel updates.

Support

This image is published and supported by SecureAnyCloud with 24/7 support. For questions about Dicoogle itself, see the Dicoogle website, the Learning Pack and the GitHub project. Dicoogle is licensed under the GNU General Public License v3.0 or later.

Deploy on Azure

Launch Dicoogle Open Source PACS on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure