Setting up my Orthanc Pacs server

I am trying to set up my Orthanc server, however I cannot even access the keycloak auth page. I am expecting the keycloak auth interface at https://pacs.biomediqa.com

My configurations so far

nginx

server {

 server_name biomediqa.com pacs.biomediqa.com;

    # Common settings for handling large files, headers, and buffer sizes
    # To avoid 504 error when uploading big files
    proxy_read_timeout 60s;

    # To avoid "too big header... / 502 Bad Gateway" error
    proxy_buffer_size   32k;
    proxy_buffers   64 8k;
    proxy_busy_buffers_size   48k;

    # To avoid "414 Request-URI Too Large" when opening multiple studies in OHIF
    large_client_header_buffers 8 16k;

    # Location for Keycloak Authentication
    # Location for Keycloak Authentication
    location /keycloak/ {
        # Forward requests to the Keycloak authentication server
        proxy_pass https://pacs.biomediqa.com/auth/realms/orthanc/account;
        # Rewrite the URL to match expected Keycloak path
        rewrite ^/keycloak/(.*) /$1 break;
        
        # Set necessary headers for Keycloak
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Forwarded "for=$proxy_add_x_forwarded_for;proto=$scheme";
        
        # Disable proxy request buffering
        proxy_request_buffering off;
        proxy_max_temp_file_size 0;
        
        # Set client size limits for large requests
        client_max_body_size 0;
    }

    # Default redirect for the root location
    location / {
        return 301 /orthanc/ui/app/;
    }

    # Location for Orthanc
    location /orthanc/ {
        proxy_pass http://orthanc:8042;
        rewrite /orthanc(.*) $1 break;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_request_buffering off;
        proxy_max_temp_file_size 0;
        client_max_body_size 0;
    }

    # Location for OHIF viewer
    location /ohif/ {
        proxy_pass http://ohif:80;
        rewrite /ohif(.*) $1 break;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Forwarded "for=$proxy_add_x_forwarded_for;proto=$scheme";
        proxy_request_buffering off;
        proxy_max_temp_file_size 0;
        client_max_body_size 0;
    }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


    add_header Strict-Transport-Security "max-age=31536000" always; # managed by Certbot


    ssl_trusted_certificate /etc/letsencrypt/live/pacs.biomediqa.com/chain.pem; # managed by Certbot
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot

}
server {
    if ($host = pacs.biomediqa.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


 listen 80;
 listen [::]:80;

 index index.html index.htm index.nginx-debian.html;

 server_name biomediqa.com pacs.biomediqa.com;

 location / {
  try_files $uri $uri/ =404;
  proxy_buffer_size   128k;
  proxy_buffers   4 256k;
  proxy_busy_buffers_size   256k;
 }

}

Orthanc

services:

  nginx:
    image: orthancteam/orthanc-nginx-certbot:main
    depends_on: [orthanc, orthanc-auth-service, keycloak]
    restart: unless-stopped
    #ports: ["80:80"]
    ports: ["443:443"]
    volumes:
      - /etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem:/etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem
      - /etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem:/etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem
      - /etc/letsencrypt/live/pacs.biomediqa.com/chain.pem:/etc/letsencrypt/live/pacs.biomediqa.com/chain.pem
      - /etc/nginx/sites-enabled/pacs.biomediqa.com.conf:/etc/nginx/sites-enabled/pacs.biomediqa.com.conf
      - /etc/nginx/sites-available/pacs.biomediqa.com.conf:/etc/nginx/sites-available/pacs.biomediqa.com.conf
    environment:
      ENABLE_ORTHANC: "true"
      ENABLE_KEYCLOAK: "true"
      ENABLE_ORTHANC_TOKEN_SERVICE: "false"
      ENABLE_HTTPS: "true"
      ENABLE_MEDDREAM: "true"
      ENABLE_ORTHANC_FOR_API: "true"
      ENABLE_OHIF: "true"
      DOMAIN_NAME: "pacs.biomediqa.com"
      CERTBOT_EMAIL: "hidden"
      CERTBOT_AUTHENTICATOR: "nginx"

  orthanc:
    image: orthancteam/orthanc
    depends_on: [postgres]
    volumes:
      - orthanc-storage:/var/lib/orthanc/db:Z
    restart: unless-stopped
    environment:
      ORTHANC__NAME: "Orthanc"
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      VOLVIEW_PLUGIN_ENABLED: "true"
      ORTHANC_JSON: |
        {
          "OrthancExplorer2": {
            "UiOptions": {
              "StudyListSearchMode": "search-button",
              "StudyListContentIfNoSearch": "empty",
              "ShowSamePatientStudiesFilter": ["PatientBirthDate", "PatientID"],
              "MedDreamViewerPublicRoot": "https://pacs.biomediqa.com/meddream/",
              "EnableApiViewMenu": true,
              "EnableShares": true,
              "DefaultShareDuration": 90,
              "ShareDurations": [0, 7, 15, 30, 90, 365],
              "EnableOpenInOhifViewer3": true,
              "EnableOpenInMedDreamViewer": true,
              "OhifViewer3PublicRoot": "https://pacs.biomediqa.com/ohif/"
            },
            "Tokens" : {
              "InstantLinksValidity": 3600,
              "ShareType": "meddream-viewer-publication"
            },
            "Keycloak" : {
              "Enable": true,
              "Url": "https://pacs.biomediqa.com/keycloak/",
              "Realm": "orthanc",
              "ClientId": "orthanc"
            }
          },
          "DicomWeb": {
            "PublicRoot": "/orthanc/dicom-web/"
          },
          "StoneWebViewer": {
            "ShowInfoPanelAtStartup": "Never"
          },
          "PostgreSQL": {
            "Host": "postgres"
          },
          "AuthenticationEnabled": false,     // because it is handled by the authorization plugin
          "Authorization": {
            "WebServiceRootUrl": "http://orthanc-auth-service:8000/",
            "WebServiceUsername": "biomediqa",
            "StandardConfigurations" : [
              "stone-webviewer",
              "orthanc-explorer-2",
              "ohif",
              "volview"
            ],
            "CheckedLevel": "studies",
            "TokenHttpHeaders" : [ "api-key" ],
            "UncheckedResources" : [
              "/app/images/unsupported.png"]
          }
        }
    secrets:
      - ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD

  postgres:
    image: postgres:16
    restart: unless-stopped
    volumes:
       - orthanc-db:/var/lib/postgresql/data:Z
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"

  orthanc-auth-service:
    image: orthancteam/orthanc-auth-service:main
    depends_on: [keycloak]
    restart: unless-stopped
    environment:
      ENABLE_KEYCLOAK: "true"
      PUBLIC_ORTHANC_ROOT: "https://pacs.biomediqa.com/orthanc/"
      PUBLIC_LANDING_ROOT: "https://pacs.biomediqa.com/orthanc/ui/app/token-landing.html"
      PERMISSIONS_FILE_PATH: "/orthanc_auth_service/permissions.json"
      ENABLE_KEYCLOAK_API_KEYS: "true"
      PUBLIC_OHIF_ROOT: "https://pacs.biomediqa.com/ohif/"
      MEDDREAM_TOKEN_SERVICE_URL: "http://meddream-token-service:8088/v3/generate"
      PUBLIC_MEDDREAM_ROOT: "https://pacs.biomediqa.com/meddream/"
    env_file:
      - ./secrets/orthanc-token.secret.env
    secrets:
      - SECRET_KEY
      - KEYCLOAK_CLIENT_SECRET
    volumes:
      - ./permissions.json:/orthanc_auth_service/permissions.json

  ohif:
    image: orthancteam/ohif-v3:main
    volumes:
      - ./ohif-app-config.js:/usr/share/nginx/html/app-config.js
    restart: unless-stopped

  meddream-token-service:
    image: orthancteam/meddream-token-service:main
    restart: unless-stopped

  meddream-viewer:
    image: orthancteam/meddream-viewer:main
    restart: unless-stopped
    depends_on:
      - orthanc-for-api
    environment:
      integration: "study"
      TOKEN_SERVICE_ADDRESS: "http://meddream-token-service:8088/v3/validate"
      ORTHANC_BASE_URL: "http://orthanc-for-api:8042"
      ORTHANC_USER: "orthanc"
      MEDDREAM_PACS_CONFIG_TYPE: "Dicomweb"
    env_file:
      - ./secrets/meddream.secret.env
    volumes:
      - meddream-license:/opt/meddream/license

  # An orthanc dedicated for API accesses and also used by MedDream
  orthanc-for-api:
    image: orthancteam/orthanc
    volumes:
      - orthanc-storage:/var/lib/orthanc/db:Z
      - ./meddream-plugin.py:/scripts/meddream-plugin.py
    depends_on: [postgres]
    restart: unless-stopped
    environment:
      ORTHANC__NAME: "Orthanc for API"
      ORTHANC__DATABASE_SERVER_IDENTIFIER: "orthanc2"
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      ORTHANC__AUTHENTICATION_ENABLED: "true"
      DICOM_WEB_PLUGIN_ENABLED: "true"
      ORTHANC__DICOM_WEB__PUBLIC_ROOT: "/orthanc-api/dicom-web/"
      ORTHANC__POSTGRESQL__HOST: "postgres"
      ORTHANC__POSTGRESQL__INDEX_CONNECTIONS_COUNT: 40
      ORTHANC__ORTHANC_EXPLORER_2__UI_OPTIONS__MED_DREAM_VIEWER_PUBLIC_ROOT: "https://pacs.biomediqa.com/meddream/"
    secrets:
      - orthanc-api.secret.json

  keycloak:
    image: orthancteam/orthanc-keycloak:main
    depends_on: [keycloak-db]
    restart: unless-stopped
    environment:
      KC_BOOTSTRAP_ADMIN_USERNAME: "admin"
      KC_DB: "postgres"
      KC_DB_URL: "jdbc:postgresql://keycloak-db:5432/keycloak"
      KC_DB_USERNAME: "keycloak"
      KC_DB_PASSWORD: "keycloak"
      KC_HOSTNAME: "https://pacs.biomediqa.com/keycloak"
    secrets:
      - KC_BOOTSTRAP_ADMIN_PASSWORD

  keycloak-db:
    image: postgres:16
    restart: unless-stopped
    volumes:
      - keycloak-db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: "keycloak"
      POSTGRES_USER: "keycloak"
      POSTGRES_DB: "keycloak"

volumes:
  keycloak-db:
  orthanc-storage:
  orthanc-db:
  meddream-license:

secrets:
  ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD:
    file: ./secrets/ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD
  SECRET_KEY:
    file: ./secrets/SECRET_KEY
  KEYCLOAK_CLIENT_SECRET:
    file: ./secrets/KEYCLOAK_CLIENT_SECRET
  KC_BOOTSTRAP_ADMIN_PASSWORD:
    file: ./secrets/KC_BOOTSTRAP_ADMIN_PASSWORD
  orthanc-api.secret.json:
    file: ./secrets/orthanc-api.secret.json

I only get this

Hello @Alex_Vergara

From your description, the issue seems to be with the Nginx/Keycloak routing.
Here are the main points to check:

  1. DNS/SSL

    • Make sure pacs.biomediqa.com resolves correctly and the SSL certificate matches the domain.
  2. Nginx block

    • Be careful with the rewrite in /keycloak/, it may break the path.

    • Test without rewrite, just proxy_pass + standard headers (Host, X-Forwarded-For, X-Forwarded-Proto).

    • Ensure location / is not intercepting requests before /keycloak/.

  3. Keycloak health

    • Confirm the Keycloak container is running and reachable:

      curl http://keycloak:8080/
      
      
  4. Orthanc/Auth Service config

    • PUBLIC_ORTHANC_ROOT and PUBLIC_LANDING_ROOT must exactly match the URLs exposed by Nginx.
  5. Keycloak Redirect URIs

    • In the Orthanc client in Keycloak, add redirect URIs like https://pacs.biomediqa.com/keycloak/*.
  6. Quick tests

    • Run:

      curl -v https://pacs.biomediqa.com/keycloak/realms/master/
      
      

      to check the HTTP response.

    • If Keycloak works when exposing 8080 directly, the issue is only with the proxy.

Most likely cause: the location /keycloak/ block in Nginx — fix proxy_pass + headers and align the URL with Keycloak and Orthanc configs.

Review the points, test them, and give us feedback. It’s important for the community.

The entire context was based on the Orthanc Book. But of course, I could be wrong. :upside_down_face:

Regards

Lucas Weber

1 Like

curl: (6) Could not resolve host: keycloak

And

$ curl -v https://pacs.biomediqa.com/keycloak/realms/master/
* Host pacs.biomediqa.com:443 was resolved.
* IPv6: (none)
* IPv4: 37.59.167.172
*   Trying 37.59.167.172:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / id-ecPublicKey
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: CN=pacs.biomediqa.com
*  start date: Oct  2 07:47:44 2025 GMT
*  expire date: Dec 31 07:47:43 2025 GMT
*  subjectAltName: host "pacs.biomediqa.com" matched cert's "pacs.biomediqa.com"
*  issuer: C=US; O=Let's Encrypt; CN=E8
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to pacs.biomediqa.com (37.59.167.172) port 443
* using HTTP/1.x
> GET /keycloak/realms/master/ HTTP/1.1
> Host: pacs.biomediqa.com
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 502 Bad Gateway
< Server: nginx/1.29.0
< Date: Thu, 09 Oct 2025 12:25:49 GMT
< Content-Type: text/html
< Content-Length: 157
< Connection: keep-alive
<
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.29.0</center>
</body>
</html>
* Connection #0 to host pacs.biomediqa.com left intact
$ curl http://ohif:80/
curl: (6) Could not resolve host: ohif
$ curl http://orthanc:8042/
curl: (6) Could not resolve host: orthanc

I think this is happening, https://pacs.biomediqa.com/ redirects me directly to https://pacs.biomediqa.com/orthanc/ui/app/ without asking keycloak.

But thinking better, since keycloak fails, it gets bypassed I think

The 502 Bad Gateway error from:

curl -v https://pacs.biomediqa.com/keycloak/realms/master/

shows that Nginx cannot reach the Keycloak container. This is a networking/proxy issue, not Orthanc itself.

Please check:

  1. All containers (nginx, keycloak, orthanc, auth-service) must be in the same Docker network. From inside the nginx container, test:

    ping keycloak
    curl http://keycloak:8080/
    
    

    If this fails, fix the service name or network.

  2. Ensure the Nginx block for /keycloak/ is placed before the location / block, otherwise / may catch everything and go directly to Orthanc.

  3. Once you can access https://pacs.biomediqa.com/keycloak/realms/master/ without 502, the Orthanc Auth Service will properly redirect you to Keycloak for login.

So the first step is: fix the Nginx ↔ Keycloak connectivity.

1 Like

Everything is as all is build and created by docker compose on the same machine. I just need to expose Orthanc to the public

Check the nginx conf file I have posted, I even moved the / location to the end. Still the same issue

new configurations, the problem persists

NGINX

server {

 server_name biomediqa.com pacs.biomediqa.com;

    # Common settings for handling large files, headers, and buffer sizes
# To avoid 504 error when uploading big files
proxy_read_timeout proxy_read_timeout_placeholder;

# To avoid "too big header... / 502 Bad Gateway" error (inspired from https://www.getpagespeed.com/server-setup/nginx/tuning-proxy_buffer_size-in-nginx)
proxy_buffer_size   32k;
proxy_buffers   64 8k;
proxy_busy_buffers_size   48k;

# To avoid "414 Request-URI Too Large" whant opening 15(!) studies in OHIF
large_client_header_buffers 8 16k;

# Fix CVE-2000-0649 - Prevent internal IP disclosure
server_name_in_redirect off;
port_in_redirect off;
absolute_redirect off;

    # Location for Keycloak Authentication
    location /keycloak/ {
                proxy_pass http://keycloak:8080;
		rewrite /keycloak(.*) $1 break;
		proxy_set_header Host $host;
		# don't know which are KeyCloak's preferred headers so include them all
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Host $host;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header Forwarded "for=$proxy_add_x_forwarded_for;proto=$scheme;host=$host";
		proxy_set_header X-Real-IP $remote_addr;
		proxy_request_buffering off;
		proxy_max_temp_file_size 0;
		client_max_body_size 0;
    }

    # Location for Orthanc
    location /orthanc/ {
		proxy_pass http://orthanc:8042;
		rewrite /orthanc(.*) $1 break;
		proxy_set_header Host $http_host;
		# Orthanc DICOMWeb supports the Forwarded header -> don't use X-Forwarded-Host/Proto headers since Forwarded header is the most standard compliant one
		proxy_set_header Forwarded "for=$proxy_add_x_forwarded_for;proto=$scheme;host=$host";
		proxy_request_buffering off;
		proxy_max_temp_file_size 0;
		client_max_body_size 0;
    }

    # Location for Orthanc-api
    location /orthanc-api/ {
		proxy_pass http://orthanc-for-api:8042;
		rewrite /orthanc-api(.*) $1 break;
		proxy_set_header Host $http_host;
		# Orthanc DICOMWeb supports the Forwarded header -> don't use X-Forwarded-Host/Proto headers since Forwarded header is the most standard compliant one
		proxy_set_header Forwarded "for=$proxy_add_x_forwarded_for;proto=$scheme;host=$host";
		proxy_request_buffering off;
		proxy_max_temp_file_size 0;
		client_max_body_size 0;
	}

    # Location for MedDream
    location /meddream/ {
		proxy_pass http://meddream-viewer:8080;
		rewrite /meddream(.*) $1 break;
		proxy_set_header Host $http_host;
		# don't know which are MedDream's preferred headers so include them all
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Host $host;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header Forwarded "for=$proxy_add_x_forwarded_for;proto=$scheme;host=$host";
		proxy_request_buffering off;
		proxy_max_temp_file_size 0;
		client_max_body_size 0;
	}

    # Location for OHIF viewer
    location /ohif/ {
		proxy_pass http://ohif:80;
		rewrite /ohif(.*) $1 break;
		proxy_set_header Host $host;
		# don't know which are OHIF's preferred headers so include them all
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Host $host;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header Forwarded "for=$proxy_add_x_forwarded_for;proto=$scheme;host=$host";
		proxy_set_header X-Real-IP $remote_addr;
		proxy_request_buffering off;
		proxy_max_temp_file_size 0;
		client_max_body_size 0;
    }

    # Default redirect for the root location
    location / {
        return 301 /orthanc/ui/app/;
    }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


    add_header Strict-Transport-Security "max-age=31536000" always; # managed by Certbot


    ssl_trusted_certificate /etc/letsencrypt/live/pacs.biomediqa.com/chain.pem; # managed by Certbot
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot

}
server {
    if ($host = pacs.biomediqa.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


 listen 80;

 location / {
   return 301 /orthanc/ui/app/;
 }

}

Orthanc

services:

  nginx:
    image: orthancteam/orthanc-nginx-certbot:main
    depends_on: [orthanc, orthanc-auth-service, orthanc-for-api, meddream-viewer, keycloak]
    restart: unless-stopped
    #ports: ["80:80"]
    ports: ["443:443"]
    volumes:
      - /etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem:/etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem
      - /etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem:/etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem
      - /etc/letsencrypt/live/pacs.biomediqa.com/chain.pem:/etc/letsencrypt/live/pacs.biomediqa.com/chain.pem
      - /etc/nginx/sites-enabled/pacs.biomediqa.com.conf:/etc/nginx/sites-enabled/pacs.biomediqa.com.conf
      - /etc/nginx/sites-available/pacs.biomediqa.com.conf:/etc/nginx/sites-available/pacs.biomediqa.com.conf
    environment:
      ENABLE_ORTHANC: "true"
      ENABLE_KEYCLOAK: "true"
      ENABLE_ORTHANC_TOKEN_SERVICE: "false"
      ENABLE_HTTPS: "true"
      ENABLE_MEDDREAM: "true"
      ENABLE_ORTHANC_FOR_API: "true"
      ENABLE_OHIF: "true"
      DOMAIN_NAME: "pacs.biomediqa.com"
      CERTBOT_EMAIL: "hidden"
      CERTBOT_AUTHENTICATOR: "nginx"

  orthanc:
    image: orthancteam/orthanc
    depends_on: [postgres]
    volumes:
      - orthanc-storage:/var/lib/orthanc/db:Z
    restart: unless-stopped
    environment:
      ORTHANC__NAME: "Orthanc"
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      VOLVIEW_PLUGIN_ENABLED: "true"
      ORTHANC_JSON: |
        {
          "OrthancExplorer2": {
            "UiOptions": {
              "StudyListSearchMode": "search-button",
              "StudyListContentIfNoSearch": "empty",
              "ShowSamePatientStudiesFilter": ["PatientBirthDate", "PatientID"],
              "MedDreamViewerPublicRoot": "https://pacs.biomediqa.com/meddream/",
              "EnableApiViewMenu": true,
              "EnableShares": true,
              "DefaultShareDuration": 90,
              "ShareDurations": [0, 7, 15, 30, 90, 365],
              "EnableOpenInOhifViewer3": true,
              "EnableOpenInMedDreamViewer": true,
              "OhifViewer3PublicRoot": "https://pacs.biomediqa.com/ohif/"
            },
            "Tokens" : {
              "InstantLinksValidity": 3600,
              "ShareType": "meddream-viewer-publication"
            },
            "Keycloak" : {
              "Enable": true,
              "Url": "https://pacs.biomediqa.com/keycloak",
              "Realm": "orthanc",
              "ClientId": "orthanc"
            }
          },
          "DicomWeb": {
            "PublicRoot": "/orthanc/dicom-web/"
          },
          "StoneWebViewer": {
            "ShowInfoPanelAtStartup": "Never"
          },
          "PostgreSQL": {
            "Host": "postgres"
          },
          "AuthenticationEnabled": false,     // because it is handled by the authorization plugin
          "Authorization": {
            "WebServiceRootUrl": "http://orthanc-auth-service:8000/",
            "WebServiceUsername": "biomediqa",
            "StandardConfigurations" : [
              "stone-webviewer",
              "orthanc-explorer-2",
              "ohif",
              "volview"
            ],
            "CheckedLevel": "studies",
            "TokenHttpHeaders" : [ "api-key" ],
            "UncheckedResources" : [
              "/app/images/unsupported.png"]
          }
        }
    secrets:
      - ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD

  postgres:
    image: postgres:16
    restart: unless-stopped
    volumes:
       - orthanc-db:/var/lib/postgresql/data:Z
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"

  orthanc-auth-service:
    image: orthancteam/orthanc-auth-service:main
    # always disable this port mapping in production !!!
    # ports: ["8000:8000"]
    depends_on: [keycloak, meddream-token-service]
    restart: unless-stopped
    environment:
      ENABLE_KEYCLOAK: "true"
      KEYCLOAK_URI: "http://keycloak:8080/realms/orthanc"
      KEYCLOAK_REALM: "orthanc"
      KEYCLOAK_CLIENT_ID: "orthanc"
      KEYCLOAK_ADMIN_URI: "http://keycloak:8080/realms/master"
      PUBLIC_ORTHANC_ROOT: "https://pacs.biomediqa.com/orthanc/"
      PUBLIC_LANDING_ROOT: "https://pacs.biomediqa.com/orthanc/ui/app/token-landing.html"
      PERMISSIONS_FILE_PATH: "/orthanc_auth_service/permissions.json"
      ENABLE_KEYCLOAK_API_KEYS: "true"
      PUBLIC_OHIF_ROOT: "https://pacs.biomediqa.com/ohif/"
      MEDDREAM_TOKEN_SERVICE_URL: "http://meddream-token-service:8088/v3/generate"
      PUBLIC_MEDDREAM_ROOT: "https://pacs.biomediqa.com/meddream/"
    env_file:
      - ./secrets/orthanc-token.secret.env
    secrets:
      - SECRET_KEY
      - KEYCLOAK_CLIENT_SECRET
    volumes:
      - ./permissions.json:/orthanc_auth_service/permissions.json

  ohif:
    image: orthancteam/ohif-v3:main
    volumes:
      - ./ohif-app-config.js:/usr/share/nginx/html/app-config.js
    restart: unless-stopped

  meddream-token-service:
    image: orthancteam/meddream-token-service:main
    restart: unless-stopped

  meddream-viewer:
    image: orthancteam/meddream-viewer:main
    restart: unless-stopped
    depends_on:
      - orthanc-for-api
    environment:
      integration: "study"
      TOKEN_SERVICE_ADDRESS: "http://meddream-token-service:8088/v3/validate"
      ORTHANC_BASE_URL: "http://orthanc-for-api:8042"
      ORTHANC_USER: "orthanc"
      MEDDREAM_PACS_CONFIG_TYPE: "Dicomweb"
    env_file:
      - ./secrets/meddream.secret.env
    volumes:
      - meddream-license:/opt/meddream/license

  # An orthanc dedicated for API accesses and also used by MedDream
  orthanc-for-api:
    image: orthancteam/orthanc
    volumes:
      - orthanc-storage:/var/lib/orthanc/db:Z
      - ./meddream-plugin.py:/scripts/meddream-plugin.py
    depends_on: [postgres]
    restart: unless-stopped
    environment:
      ORTHANC__NAME: "Orthanc for API"
      ORTHANC__DATABASE_SERVER_IDENTIFIER: "orthanc2"
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      ORTHANC__AUTHENTICATION_ENABLED: "true"
      DICOM_WEB_PLUGIN_ENABLED: "true"
      ORTHANC__DICOM_WEB__PUBLIC_ROOT: "/orthanc-api/dicom-web/"
      ORTHANC__POSTGRESQL__HOST: "postgres"
      ORTHANC__POSTGRESQL__INDEX_CONNECTIONS_COUNT: 40
      ORTHANC__ORTHANC_EXPLORER_2__UI_OPTIONS__MED_DREAM_VIEWER_PUBLIC_ROOT: "https://pacs.biomediqa.com/meddream/"
    secrets:
      - orthanc-api.secret.json

  keycloak:
    image: orthancteam/orthanc-keycloak:main
    depends_on: [keycloak-db]
    restart: unless-stopped
    environment:
      KC_BOOTSTRAP_ADMIN_USERNAME: "admin"
      KC_DB: "postgres"
      KC_DB_URL: "jdbc:postgresql://keycloak-db:5432/keycloak"
      KC_DB_USERNAME: "keycloak"
      KC_DB_PASSWORD: "keycloak"
      KC_HOSTNAME: "https://pacs.biomediqa.com/keycloak"
    secrets:
      - KC_BOOTSTRAP_ADMIN_PASSWORD

  keycloak-db:
    image: postgres:16
    restart: unless-stopped
    volumes:
      - keycloak-db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: "keycloak"
      POSTGRES_USER: "keycloak"
      POSTGRES_DB: "keycloak"

volumes:
  keycloak-db:
  orthanc-storage:
  orthanc-db:
  meddream-license:

secrets:
  ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD:
    file: ./secrets/ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD
  SECRET_KEY:
    file: ./secrets/SECRET_KEY
  KEYCLOAK_CLIENT_SECRET:
    file: ./secrets/KEYCLOAK_CLIENT_SECRET
  KC_BOOTSTRAP_ADMIN_PASSWORD:
    file: ./secrets/KC_BOOTSTRAP_ADMIN_PASSWORD
  orthanc-api.secret.json:
    file: ./secrets/orthanc-api.secret.json

MedDream somehow shows the login screen, but it doesn’t accept any user

I don’t need MedDream, this is just a test

Hi,

Looking at your configuration, there are a few key points to check:

  1. Nginx rewrites

    • Lines like:

      rewrite /keycloak(.*) $1 break;
      
      

      usually break Keycloak, because the /keycloak/ prefix is needed.
      → Remove the rewrite and just use proxy_pass ``http://keycloak:8080/;.
      Do the same for /orthanc/, /orthanc-api/, /meddream/ and /ohif/.

  2. Keycloak hostname

    • In your docker-compose you have:

      KC_HOSTNAME: "https://pacs.biomediqa.com/keycloak"
      
      

      This should be only:

      KC_HOSTNAME: "pacs.biomediqa.com"
      
      

      The /keycloak/ prefix is handled by Nginx, not by Keycloak itself.

  3. Redirect URIs

    • In the Keycloak client, make sure you add:

      https://pacs.biomediqa.com/keycloak/*
      https://pacs.biomediqa.com/orthanc/*
      https://pacs.biomediqa.com/ohif/*
      
      

      Missing redirect URIs will prevent the login from working.

  4. Tests

    • Check if Keycloak is reachable through Nginx:

      curl -vk https://pacs.biomediqa.com/keycloak/realms/master/
      
      

      You should get JSON, not 404/502.

    • Expose temporarily 8080:8080 for the Keycloak container and check if http://localhost:8080/ works.
      If it does, the issue is only with the Nginx proxy.

Most likely cause: the rewrite rules in Nginx and the KC_HOSTNAME variable.

Try removing the rewrites + adjust KC_HOSTNAME, then confirm your redirect URIs in Keycloak.

Hi Alex,

I’ve just had a look on your configuration and some things look a little bit weird:

  • I noticed that you use the main tag for the Docker images. I recommend to use an exact version to avoid any trouble. Today, 25.9.0 is the tag to use for all the services (nginx, keycloak,…) and 25.10.3 is the tag to use for both Orthanc containers.
  • For the Nginx container, as soon as you use the “certbot” version, there is no need for volumes, so you should remove all of them. The other way is to use the regular nginx https://hub.docker.com/r/orthancteam/orthanc-nginx without certbot) and provide a certificate on your own.
  • Still for Nginx, if you need certbot/let’encrypt, you have to choose the challenge type (see Challenge Types - Let's Encrypt ). If it’s HTTP-01 (most common), you have to forward both the 80 and 443 ports. And you should remove these 2 lines:
    ENABLE_HTTPS: "true"
    CERTBOT_AUTHENTICATOR: "nginx"
    If it’s DNS-01, you still have to remove ENABLE_HTTPS and follow the instructions here for the CERTBOT_AUTHENTICATOR value.

I would recommend to apply these modifications, remove all the volumes and containers and recreate the setup.

Then, don’t hesitate to share the logs of each container, it may help us to help you :wink:

@byweber Thank you very much for the time you spend on this forum helping others! May I kindly ask that you share tips and recommendations based only on the documentation and your personal experience with Orthanc?
The goal is to avoid a model collapse…

Hope this helps!

1 Like

Ok, so far:

  1. I have removed certbot from the docker as I need to use the local certbot, so I must leave the volumes for the pem files (ENABLE_HTTPS=true). I have already generated my certificates for other services.
  2. I have added the exact versions 25.9.0.
  3. I have removed meddream as I don’t need it.

Problems:

  1. Using orthanc-nginx alone doesn’t load my certificates and refuses to use them. I can’t no longer connect through https, so I went back to orthanc-nginx-certbot
  2. Without the volumes I got this error
    1. 2025/10/10 07:52:15 [warning] Could not find non-zero size keyfile file '/etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem' in '/etc/nginx/conf.d/orthanc-nginx-certbot.conf.nokey'
      2025/10/10 07:52:15 [warning] Could not find non-zero size fullchain file '/etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem' in '/etc/nginx/conf.d/orthanc-nginx-certbot.conf.nokey'
      2025/10/10 07:52:15 [warning] Could not find non-zero size chain file '/etc/letsencrypt/live/pacs.biomediqa.com/chain.pem' in '/etc/nginx/conf.d/orthanc-nginx-certbot.conf.nokey'
      
  3. Running with the previous config for nginx this is the error I get
    1. 2025/10/10 07:59:46 [info] Starting the Nginx service
      2025/10/10 07:59:46 [info] Running the autorenewal service
      2025/10/10 07:59:46 [emerg] 65#65: host not found in upstream "keycloak" in /etc/nginx/enabled-reverse-proxies/reverse-proxy.keycloak-https.conf:7
      nginx: [emerg] host not found in upstream "keycloak" in /etc/nginx/enabled-reverse-proxies/reverse-proxy.keycloak-https.conf:7
      total 48
      

Using this conf in the docker compose I no longer need the personalized nginx conf

services:

  nginx:
    image: orthancteam/orthanc-nginx-certbot:25.9.0
    depends_on: [orthanc, orthanc-auth-service, orthanc-for-api, keycloak]
    restart: unless-stopped
    #ports: ["80:80"]
    ports: ["443:443"]
    volumes:
      - /etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem:/etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem
      - /etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem:/etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem
      - /etc/letsencrypt/live/pacs.biomediqa.com/chain.pem:/etc/letsencrypt/live/pacs.biomediqa.com/chain.pem
    environment:
      ENABLE_ORTHANC: "true"
      ENABLE_KEYCLOAK: "true"
      ENABLE_ORTHANC_TOKEN_SERVICE: "false"
      ENABLE_ORTHANC_FOR_SHARES: "true"
      ENABLE_MEDDREAM: "false"
      ENABLE_ORTHANC_FOR_API: "true"
      ENABLE_OHIF: "true"
      DOMAIN_NAME: "pacs.biomediqa.com"
      CERTBOT_EMAIL: "hidden"
      STAGING: 1

  orthanc:
    image: orthancteam/orthanc:25.9.0
    depends_on: [orthanc-db]
    volumes:
      - orthanc-storage:/var/lib/orthanc/db
    restart: unless-stopped
    environment:
      ORTHANC__NAME: "Orthanc"
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      VOLVIEW_PLUGIN_ENABLED: "true"
      ORTHANC_JSON: |
        {
          "OrthancExplorer2": {
            "UiOptions": {
              "StudyListSearchMode": "search-button",
              "StudyListContentIfNoSearch": "empty",
              "ShowSamePatientStudiesFilter": ["PatientBirthDate", "PatientID"],
              "EnableApiViewMenu": true,
              "EnableShares": true,
              "DefaultShareDuration": 90,
              "ShareDurations": [0, 7, 15, 30, 90, 365],
              "EnableOpenInOhifViewer3": true,
              "OhifViewer3PublicRoot": "https://pacs.biomediqa.com/ohif/"
            },
            "Tokens" : {
              "InstantLinksValidity": 3600,
              "ShareType": "stone-viewer-publication",
              "LandingOptions" : [
                {
                  "Type" : "open-viewer-button"
                },
                {
                  "Type" : "download-study"
                },
                {
                  "Type": "custom",
                  "Id": "get-jpeg-archive",
                  "Icon": "bi bi-filetype-jpg",
                  "Title": "Download study as jpeg Archive",
                  "Url": "../../studies/{UUID}/download-as-jpeg-archive?preview-level=instance&filename={StudyInstanceUID}.zip"
                }
              ]
            },
            "Keycloak" : {
              "Enable": true,
              "Url": "https://pacs.biomediqa.com/keycloak",
              "Realm": "orthanc",
              "ClientId": "orthanc"
            }
          },
          "DicomWeb": {
            "PublicRoot": "/orthanc/dicom-web/"
          },
          "StoneWebViewer": {
            "ShowInfoPanelAtStartup": "Never"
          },
          "PostgreSQL": {
            "Host": "postgres"
          },
          "AuthenticationEnabled": false,     // because it is handled by the authorization plugin
          "Authorization": {
            "WebServiceRootUrl": "http://orthanc-auth-service:8000/",
            "WebServiceUsername": "biomediqa",
            "StandardConfigurations" : [
              "stone-webviewer",
              "orthanc-explorer-2",
              "ohif",
              "volview"
            ],
            "CheckedLevel": "studies",
            "TokenHttpHeaders" : [ "api-key" ],
            "UncheckedResources" : [
              "/app/images/unsupported.png"]
          },
          "DicomWeb": {
            "Enable": true,
            "PublicRoot": "/orthanc/dicom-web/"
          }
        }
    secrets:
      - ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD

  orthanc-db:
    image: postgres:16
    restart: unless-stopped
    volumes:
       - orthanc-db:/var/lib/postgresql/data
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"

  orthanc-for-shares:
    image: orthancteam/orthanc:25.9.0
    volumes:
      - orthanc-storage:/var/lib/orthanc/db
    depends_on: [orthanc-db]
    restart: unless-stopped
    environment:
      STONE_WEB_VIEWER_PLUGIN_ENABLED: "true"
      DICOM_WEB_PLUGIN_ENABLED: "true"
      ORTHANC__POSTGRESQL__HOST: "orthanc-db"
      ORTHANC__POSTGRESQL__TRANSACTION_MODE: "ReadCommitted"
      ORTHANC_JSON: |
        {
          "Name": "Orthanc",
          "OrthancExplorer2": {
            "IsDefaultUI": true,
            "UiOptions": {
              "EnableShares": true,
              "DefaultShareDuration": 0,
              "ShareDurations": [0, 7, 15, 30, 90, 365]
            },
            "Tokens" : {
              "ShareType": "stone-viewer-publication"
            }
          },
          "AuthenticationEnabled": false,     // because it is handled by the authorization plugin
          "Authorization": {
            "WebServiceRootUrl": "http://orthanc-auth-service:8000/",
            "WebServiceUsername": "biomediqa",
            "StandardConfigurations" : [
              "osimis-web-viewer",
              "stone-webviewer",
              "orthanc-explorer-2"    // required for the token-landing page
            ],
            "CheckedLevel": "studies"
          },
          "DicomWeb": {
            "Enable": true,
            "PublicRoot": "/orthanc/dicom-web/"
          }
        }
    secrets:
      - ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD

  orthanc-auth-service:
    image: orthancteam/orthanc-auth-service:25.9.0
    # always disable this port mapping in production !!!
    # ports: ["8000:8000"]
    depends_on: [keycloak]
    restart: unless-stopped
    environment:
      ENABLE_KEYCLOAK: "true"
      KEYCLOAK_URI: "http://keycloak:8080/realms/orthanc"
      KEYCLOAK_REALM: "orthanc"
      KEYCLOAK_CLIENT_ID: "orthanc"
      KEYCLOAK_ADMIN_URI: "http://keycloak:8080/realms/master"
      PUBLIC_ORTHANC_ROOT: "https://pacs.biomediqa.com/orthanc/"
      PUBLIC_LANDING_ROOT: "https://pacs.biomediqa.com/orthanc/ui/app/token-landing.html"
      PERMISSIONS_FILE_PATH: "/orthanc_auth_service/permissions.json"
      ENABLE_KEYCLOAK_API_KEYS: "true"
      PUBLIC_OHIF_ROOT: "https://pacs.biomediqa.com/ohif/"
    env_file:
      - ./secrets/orthanc-token.secret.env
    secrets:
      - SECRET_KEY
      - KEYCLOAK_CLIENT_SECRET
    volumes:
      - ./permissions.json:/orthanc_auth_service/permissions.json

  ohif:
    image: orthancteam/ohif-v3:25.9.0
    volumes:
      - ./ohif-app-config.js:/usr/share/nginx/html/app-config.js
    restart: unless-stopped

  # An orthanc dedicated for API accesses and also used by MedDream
  orthanc-for-api:
    image: orthancteam/orthanc:25.9.0
    volumes:
      - orthanc-storage:/var/lib/orthanc/db
    depends_on: [orthanc-db]
    restart: unless-stopped
    environment:
      ORTHANC__NAME: "Orthanc for API"
      ORTHANC__DATABASE_SERVER_IDENTIFIER: "orthanc2"
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      ORTHANC__AUTHENTICATION_ENABLED: "true"
      DICOM_WEB_PLUGIN_ENABLED: "true"
      ORTHANC__DICOM_WEB__PUBLIC_ROOT: "/orthanc-api/dicom-web/"
      ORTHANC__POSTGRESQL__HOST: "orthanc-db"
      ORTHANC__POSTGRESQL__INDEX_CONNECTIONS_COUNT: 40
      ORTHANC__POSTGRESQL__TRANSACTION_MODE: "ReadCommitted"
    secrets:
      - orthanc-api.secret.json

  keycloak:
    image: orthancteam/orthanc-keycloak:25.9.0
    depends_on: [keycloak-db]
    restart: unless-stopped
    environment:
      KC_BOOTSTRAP_ADMIN_USERNAME: "admin"
      KEYCLOAK_ADMIN: "admin"
      KC_DB: "postgres"
      KC_DB_URL: "jdbc:postgresql://keycloak-db:5432/keycloak"
      KC_DB_USERNAME: "keycloak"
      KC_DB_PASSWORD: "keycloak"
      KC_HOSTNAME: "https://pacs.biomediqa.com/keycloak"
    secrets:
      - KC_BOOTSTRAP_ADMIN_PASSWORD
      - KEYCLOAK_ADMIN_PASSWORD

  keycloak-db:
    image: postgres:16
    restart: unless-stopped
    volumes:
      - keycloak-db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: "keycloak"
      POSTGRES_USER: "keycloak"
      POSTGRES_DB: "keycloak"

volumes:
  keycloak-db:
  orthanc-storage:
  orthanc-db:
  meddream-license:

secrets:
  ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD:
    file: ./secrets/ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD
  SECRET_KEY:
    file: ./secrets/SECRET_KEY
  KEYCLOAK_CLIENT_SECRET:
    file: ./secrets/KEYCLOAK_CLIENT_SECRET
  KC_BOOTSTRAP_ADMIN_PASSWORD:
    file: ./secrets/KC_BOOTSTRAP_ADMIN_PASSWORD
  KEYCLOAK_ADMIN_PASSWORD:
    file: ./secrets/KEYCLOAK_ADMIN_PASSWORD
  orthanc-api.secret.json:
    file: ./secrets/orthanc-api.secret.json

However keycloak refuses to start

2025/10/10 08:50:17 [emerg] 65#65: host not found in upstream "keycloak" in /etc/nginx/enabled-reverse-proxies/reverse-proxy.keycloak-https.conf:7
nginx: [emerg] host not found in upstream "keycloak" in /etc/nginx/enabled-reverse-proxies/reverse-proxy.keycloak-https.conf:7

Also if I use ENABLE_ORTHANC_TOKEN_SERVICE: “true” i get this error

2025/10/10 08:55:47 [emerg] 65#65: host not found in upstream "orthanc-auth-service" in /etc/nginx/enabled-reverse-proxies/reverse-proxy.token-service.conf:8
nginx: [emerg] host not found in upstream "orthanc-auth-service" in /etc/nginx/enabled-reverse-proxies/reverse-proxy.token-service.conf:8

Final working configuration

services:

  nginx:
    image: orthancteam/orthanc-nginx-certbot:25.9.0
    depends_on: [orthanc, orthanc-auth-service, orthanc-for-api, keycloak]
    restart: unless-stopped
    #ports: ["80:80"]
    ports: ["443:443"]
    volumes:
      - /etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem:/etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem
      - /etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem:/etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem
      - /etc/letsencrypt/live/pacs.biomediqa.com/chain.pem:/etc/letsencrypt/live/pacs.biomediqa.com/chain.pem
    environment:
      ENABLE_ORTHANC: "true"
      ENABLE_KEYCLOAK: "true"
      ENABLE_ORTHANC_TOKEN_SERVICE: "true"
      ENABLE_ORTHANC_FOR_SHARES: "true"
      ENABLE_MEDDREAM: "false"
      ENABLE_ORTHANC_FOR_API: "true"
      ENABLE_OHIF: "true"
      DOMAIN_NAME: "pacs.biomediqa.com"
      CERTBOT_EMAIL: "hidden"
      STAGING: 1

  orthanc:
    image: orthancteam/orthanc:25.9.0
    depends_on: [orthanc-db]
    volumes:
      - orthanc-storage:/var/lib/orthanc/db
    restart: unless-stopped
    environment:
      ORTHANC__NAME: "Orthanc"
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      VOLVIEW_PLUGIN_ENABLED: "true"
      ORTHANC_JSON: |
        {
          "OrthancExplorer2": {
            "UiOptions": {
              "StudyListSearchMode": "search-button",
              "StudyListContentIfNoSearch": "empty",
              "ShowSamePatientStudiesFilter": ["PatientBirthDate", "PatientID"],
              "EnableApiViewMenu": true,
              "EnableShares": true,
              "DefaultShareDuration": 90,
              "ShareDurations": [0, 7, 15, 30, 90, 365],
              "EnableOpenInOhifViewer3": true,
              "OhifViewer3PublicRoot": "https://pacs.biomediqa.com/ohif/"
            },
            "Tokens" : {
              "InstantLinksValidity": 3600,
              "ShareType": "stone-viewer-publication",
              "LandingOptions" : [
                {
                  "Type" : "open-viewer-button"
                },
                {
                  "Type" : "download-study"
                },
                {
                  "Type": "custom",
                  "Id": "get-jpeg-archive",
                  "Icon": "bi bi-filetype-jpg",
                  "Title": "Download study as jpeg Archive",
                  "Url": "../../studies/{UUID}/download-as-jpeg-archive?preview-level=instance&filename={StudyInstanceUID}.zip"
                }
              ]
            },
            "Keycloak" : {
              "Enable": true,
              "Url": "https://pacs.biomediqa.com/keycloak",
              "Realm": "orthanc",
              "ClientId": "orthanc"
            }
          },
          "DicomWeb": {
            "PublicRoot": "/orthanc/dicom-web/"
          },
          "StoneWebViewer": {
            "ShowInfoPanelAtStartup": "Never"
          },
          "PostgreSQL": {
            "Host": "postgres"
          },
          "AuthenticationEnabled": false,     // because it is handled by the authorization plugin
          "Authorization": {
            "WebServiceRootUrl": "http://orthanc-auth-service:8000/",
            "WebServiceUsername": "biomediqa",
            "StandardConfigurations" : [
              "stone-webviewer",
              "orthanc-explorer-2",
              "ohif",
              "volview"
            ],
            "CheckedLevel": "studies",
            "TokenHttpHeaders" : [ "api-key" ],
            "UncheckedResources" : [
              "/app/images/unsupported.png"]
          },
          "DicomWeb": {
            "Enable": true,
            "PublicRoot": "/orthanc/dicom-web/"
          }
        }
    secrets:
      - ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD

  orthanc-db:
    image: postgres:16
    restart: unless-stopped
    volumes:
       - orthanc-db:/var/lib/postgresql/data
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"

  orthanc-for-shares:
    image: orthancteam/orthanc:25.9.0
    volumes:
      - orthanc-storage:/var/lib/orthanc/db
    depends_on: [orthanc-db]
    restart: unless-stopped
    environment:
      STONE_WEB_VIEWER_PLUGIN_ENABLED: "true"
      DICOM_WEB_PLUGIN_ENABLED: "true"
      ORTHANC__POSTGRESQL__HOST: "orthanc-db"
      ORTHANC__POSTGRESQL__TRANSACTION_MODE: "ReadCommitted"
      ORTHANC_JSON: |
        {
          "Name": "Orthanc",
          "OrthancExplorer2": {
            "IsDefaultUI": true,
            "UiOptions": {
              "EnableShares": true,
              "DefaultShareDuration": 0,
              "ShareDurations": [0, 7, 15, 30, 90, 365]
            },
            "Tokens" : {
              "ShareType": "stone-viewer-publication"
            }
          },
          "AuthenticationEnabled": false,     // because it is handled by the authorization plugin
          "Authorization": {
            "WebServiceRootUrl": "http://orthanc-auth-service:8000/",
            "WebServiceUsername": "biomediqa",
            "StandardConfigurations" : [
              "osimis-web-viewer",
              "stone-webviewer",
              "orthanc-explorer-2"    // required for the token-landing page
            ],
            "CheckedLevel": "studies"
          },
          "DicomWeb": {
            "Enable": true,
            "PublicRoot": "/orthanc/dicom-web/"
          }
        }
    secrets:
      - ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD

  orthanc-auth-service:
    image: orthancteam/orthanc-auth-service:25.9.0
    # always disable this port mapping in production !!!
    # ports: ["8000:8000"]
    depends_on: [keycloak]
    restart: unless-stopped
    environment:
      ENABLE_KEYCLOAK: "true"
      KEYCLOAK_URI: "http://keycloak:8080/realms/orthanc"
      KEYCLOAK_REALM: "orthanc"
      KEYCLOAK_CLIENT_ID: "orthanc"
      KEYCLOAK_ADMIN_URI: "http://keycloak:8080/realms/master"
      PUBLIC_ORTHANC_ROOT: "https://pacs.biomediqa.com/orthanc/"
      PUBLIC_LANDING_ROOT: "https://pacs.biomediqa.com/orthanc/ui/app/token-landing.html"
      PERMISSIONS_FILE_PATH: "/orthanc_auth_service/permissions.json"
      ENABLE_KEYCLOAK_API_KEYS: "true"
      PUBLIC_OHIF_ROOT: "https://pacs.biomediqa.com/ohif/"
    env_file:
      - ./secrets/orthanc-token.secret.env
    secrets:
      - SECRET_KEY
      - KEYCLOAK_CLIENT_SECRET
    volumes:
      - ./permissions.json:/orthanc_auth_service/permissions.json

  ohif:
    image: orthancteam/ohif-v3:25.9.0
    volumes:
      - ./ohif-app-config.js:/usr/share/nginx/html/app-config.js
    restart: unless-stopped

  # An orthanc dedicated for API accesses and also used by MedDream
  orthanc-for-api:
    image: orthancteam/orthanc:25.9.0
    volumes:
      - orthanc-storage:/var/lib/orthanc/db
    depends_on: [orthanc-db]
    restart: unless-stopped
    environment:
      ORTHANC__NAME: "Orthanc for API"
      ORTHANC__DATABASE_SERVER_IDENTIFIER: "orthanc2"
      VERBOSE_ENABLED: "true"
      VERBOSE_STARTUP: "true"
      ORTHANC__AUTHENTICATION_ENABLED: "true"
      DICOM_WEB_PLUGIN_ENABLED: "true"
      ORTHANC__DICOM_WEB__PUBLIC_ROOT: "/orthanc-api/dicom-web/"
      ORTHANC__POSTGRESQL__HOST: "orthanc-db"
      ORTHANC__POSTGRESQL__INDEX_CONNECTIONS_COUNT: 40
      ORTHANC__POSTGRESQL__TRANSACTION_MODE: "ReadCommitted"
    secrets:
      - orthanc-api.secret.json

  keycloak:
    image: orthancteam/orthanc-keycloak:25.9.0
    depends_on: [keycloak-db]
    restart: unless-stopped
    environment:
      KEYCLOAK_ADMIN: "admin"
      KC_DB: "postgres"
      KC_DB_URL: "jdbc:postgresql://keycloak-db:5432/keycloak"
      KC_DB_USERNAME: "keycloak"
      KC_DB_PASSWORD: "keycloak"
      KC_HOSTNAME: "https://pacs.biomediqa.com/keycloak"
    secrets:
      - KC_BOOTSTRAP_ADMIN_PASSWORD
      - KEYCLOAK_ADMIN_PASSWORD

  keycloak-db:
    image: postgres:16
    restart: unless-stopped
    volumes:
      - keycloak-db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: "keycloak"
      POSTGRES_USER: "keycloak"
      POSTGRES_DB: "keycloak"

volumes:
  keycloak-db:
  orthanc-storage:
  orthanc-db:
  meddream-license:

secrets:
  ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD:
    file: ./secrets/ORTHANC__AUTHORIZATION__WEB_SERVICE_PASSWORD
  SECRET_KEY:
    file: ./secrets/SECRET_KEY
  KEYCLOAK_CLIENT_SECRET:
    file: ./secrets/KEYCLOAK_CLIENT_SECRET
  KC_BOOTSTRAP_ADMIN_PASSWORD:
    file: ./secrets/KC_BOOTSTRAP_ADMIN_PASSWORD
  KEYCLOAK_ADMIN_PASSWORD:
    file: ./secrets/KEYCLOAK_ADMIN_PASSWORD
  orthanc-api.secret.json:
    file: ./secrets/orthanc-api.secret.json

Thanks for all the help!!

I ended up not needing the nginx personalized conf at all. However, the ssl conf is tricky and should be done outside first.

Now I must start configuring everything, but that’s a different topic.

Some clarifications could help:

The orthancteam/orthanc-nginx Docker image:

Is a preconfigured Nginx allowing to redirect the queries to the right services (Orthanc, Keycloak,…).

This Nginx uses port 80 if ENABLE_HTTPS is set to false.

This Nginx uses port 443 if ENABLE_HTTPS is set to true. In such a case, you have to bind your certificate and corresponding key to these container files:

  • /etc/nginx/tls/crt.pem
  • /etc/nginx/tls/key.pem

So, you should have something more or less like that in your compose:

- /etc/letsencrypt/live/pacs.biomediqa.com/fullchain.pem:/etc/nginx/tls/crt.pem
- /etc/letsencrypt/live/pacs.biomediqa.com/privkey.pem:/etc/nginx/tls/key.pem

From my understanding of your case, this is the way to go to, because you manage Let’s Encrypt, Certbot and so on, on your own way.

The orthancteam/orthanc-nginx-certbot Docker image:

Is the same preconfigured Nginx allowing to redirect the queries to the right services (Orthanc, Keycloak,…), but it also includes Certbot to manage the certificates with Let’s Encrypt.

From my understanding of your case, this is not the way to go to because you already have another Certbot handling the certs…

HTH,

2 Likes