Dicoogle Open Source PACS on Ubuntu 24.04 on Azure User Guide
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, so 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 modalities and other PACS can store to it and query it.
The cloudimg image installs the pinned official Dicoogle 3.6.0 release (OpenJDK 17) under systemd, together with the two plugins that make it a complete archive - the Lucene index/query provider and the file storage provider - then locks it down for a marketplace appliance. Dicoogle itself is bound to loopback only and nginx fronts port 80. Access is gated by Dicoogle's own account system with a password generated uniquely on every VM, and the DICOM listener is restricted to a per-VM calling-AE allow-list rather than upstream's accept-from-anyone default. Backed by 24/7 cloudimg support.
What is included:
- Dicoogle 3.6.0 installed from the pinned, checksummed official release, running as the
dicooglesystemd service under OpenJDK 17 - The Lucene index/query plugin and the filestorage plugin, pre-wired so 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 - the upstream default
dicoogle/dicoogleaccount 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
- A dedicated Azure data disk at
/var/lib/dicoogleholding the archive, the Lucene index and the configuration dicoogle.service+nginx.serviceas systemd units, enabled and active- An unauthenticated
/healthzendpoint on:80for Azure Load Balancer health probes - A synthetic self-test study stored at first boot, proving the archive stores, indexes and retrieves
- 24/7 cloudimg 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 CLOUDIMG^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
CLOUDIMG-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) and1045(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 + 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, leaving headroom for the OS and the page cache 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: 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, choose Create a resource, search the Marketplace for Dicoogle 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) and HTTP (80). Review the dedicated data disk on the Disks tab, then Review + create -> Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name dicoogle \
--image <marketplace-image-urn> \
--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; nginx fronts port 80. The DIMSE storage service listens on 6666 and query/retrieve on 1045, both owned by the Dicoogle service itself and both gated by the calling-AE allow-list.

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 - 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=CLOUDIMG'
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:CLOUDIMG-PHANTOM-001
The results appear as a Patient -> Study -> Series -> Image drill-down. Click 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:CLOUDIMG-PHANTOM-001
PatientName:CLOUDIMG*
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:CLOUDIMG-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.xmland add another<allowed-aetitles>element inside<dicom-services>for each sender, then restart Dicoogle. Keep the list explicit - removing every entry restores upstream's accept-from-anyone behaviour.text <allowed-aetitles>CLOUDIMG-a1b2c3d</allowed-aetitles> <allowed-aetitles>CT-SCANNER-01</allowed-aetitles>text sudo systemctl restart dicoogle - Open the DIMSE port to that sender only. In the Azure portal add an inbound NSG rule for
6666/tcp(and1045/tcpif the sender performs C-FIND/C-MOVE) with the source restricted to the modality's address or subnet - neverAny. - Point the modality at this VM, using the server AE title
DICOOGLE-STORAGE, the VM's address, and port6666.
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 -> Indexer and unindex, 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/tcpand443/tcpto 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.txtrestricted to root. - Patch regularly. Unattended security upgrades are enabled; reboot for kernel updates.
Support
This image is published and supported by cloudimg 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.