Karnak is distributed as a container image on Docker Hub: nroduit/karnak (multi-arch, linux/amd64 and linux/arm64). It requires two third-party services:
PostgreSQL for the persistence of the Karnak configuration (sources, destinations, projects, profiles, monitoring)
Redis for the cache (pseudonym mappings, API call results)
The recommended way to run Karnak in production is Docker Compose. This page describes how to write the docker-compose.yml file and the associated configuration from scratch.
Info
Karnak is also available as a portable distribution that runs without Docker, with an embedded database and an in-memory cache. It is intended for a single machine and local use.
Prerequisites
Docker Engine 20.10 or later with the Compose plugin (docker compose v2). See the Docker installation guide.
The commands below target Linux. On Windows or macOS, use Docker Desktop and adapt the shell commands.
1. Create the project layout
Create a folder (for example /opt/karnak) with the following structure:
Karnak reads sensitive values from files through the environment variables ending with _FILE, which is compatible with Docker secrets. Three secrets are required:
Secret file
Purpose
karnak_login_password
Password of the web portal administrator (login defined by KARNAK_LOGIN_ADMIN, admin by default)
karnak_postgres_password
Password of the PostgreSQL user, shared by the database container and Karnak
karnak_db_encryption_key
Key used by PostgreSQL (pgcrypto) to encrypt sensitive columns such as the client secrets of the API authentication configurations
Back up karnak_db_encryption_key together with the database. The key must stay identical for the whole life of the database: if it is lost or changed, the encrypted values stored in PostgreSQL can no longer be decrypted.
3. Write the docker-compose.yml file
services:karnak:image:nroduit/karnak:v2.0.0container_name:karnakrestart:unless-stoppedports:# DICOM listener: host port 11112 -> container port 11119 (DICOM_LISTENER_PORT)- "11112:11119"# Web portal (KARNAK_WEB_PORT, 8080 by default in the container)- "8080:8080"env_file:karnak.envenvironment:# Third-party services (container names on the Compose network)DB_HOST:karnak-dbDB_PORT:"5432"DB_NAME:karnakDB_USER:karnakDB_PASSWORD_FILE:/run/secrets/karnak_postgres_passwordDB_ENCRYPTION_KEY_FILE:/run/secrets/karnak_db_encryption_keyREDIS_HOST:karnak-cacheREDIS_PORT:"6379"# Web portal administratorKARNAK_LOGIN_ADMIN:adminKARNAK_LOGIN_PASSWORD_FILE:/run/secrets/karnak_login_passwordsecrets:- karnak_login_password- karnak_postgres_password- karnak_db_encryption_keydepends_on:karnak-db:condition:service_healthykarnak-cache:condition:service_healthylogging:driver:json-fileoptions:max-size:"10m"max-file:"5"networks:- karnak-netkarnak-db:image:postgres:17-alpinecontainer_name:karnak-dbrestart:unless-stoppedenvironment:POSTGRES_DB:karnakPOSTGRES_USER:karnakPOSTGRES_PASSWORD_FILE:/run/secrets/karnak_postgres_passwordvolumes:- karnak-db-data:/var/lib/postgresql/datasecrets:- karnak_postgres_passwordhealthcheck:test:["CMD-SHELL","pg_isready -U karnak -d karnak"]interval:10stimeout:5sretries:5networks:- karnak-netkarnak-cache:image:redis:7-alpinecontainer_name:karnak-cacherestart:unless-stoppedhealthcheck:test:["CMD","redis-cli","ping"]interval:10stimeout:5sretries:5networks:- karnak-netsecrets:karnak_login_password:file:secrets/karnak_login_passwordkarnak_postgres_password:file:secrets/karnak_postgres_passwordkarnak_db_encryption_key:file:secrets/karnak_db_encryption_keyvolumes:karnak-db-data:networks:karnak-net:
Notes:
Pin the Karnak image to a release tag (v2.0.0). The latest tag follows the most recent release and the master tag is the development build.
Only the ports of the karnak service are published. The database and the cache are reachable from Karnak only, through the internal Compose network.
DB_PORT is the port inside the Compose network (the default PostgreSQL port), not a port published on the host.
Karnak does not need a writable volume: the configuration is stored in PostgreSQL and the logs are written to the standard output (see Configuring Logs).
4. Write the karnak.env file
This file holds the gateway options. All variables are optional; the values below are the defaults. Uncomment and adapt what you need.
# --------------------------------------------------------# DICOM listener# --------------------------------------------------------DICOM_LISTENER_AET=KARNAK-GATEWAY
DICOM_LISTENER_PORT=11119### Listen with DICOM TLS. When true, the TLS keystore and truststore below are required.DICOM_LISTENER_TLS=false#TLS_KEYSTORE_PATH=#TLS_KEYSTORE_SECRET=#TLS_TRUSTSTORE_PATH=#TLS_TRUSTSTORE_SECRET=# --------------------------------------------------------# SMTP server (email notifications and conformance reports)# --------------------------------------------------------#MAIL_SMTP_HOST=#MAIL_SMTP_PORT=#MAIL_SMTP_SENDER=### Authentication type, "SSL" or "STARTTLS". Leave empty for an SMTP server without authentication.#MAIL_SMTP_TYPE=#MAIL_SMTP_USER=#MAIL_SMTP_SECRET=# --------------------------------------------------------# Automatic pixel de-identification (external OCR service)# --------------------------------------------------------#OCR_URL=http://localhost:8000# --------------------------------------------------------# Identity provider. IDP=oidc enables OpenID Connect, any other# value keeps the built-in administrator account.# --------------------------------------------------------IDP=undefined
#OIDC_CLIENT_ID=#OIDC_CLIENT_SECRET=#OIDC_ISSUER_URI=# --------------------------------------------------------# Logging# --------------------------------------------------------### Path of a custom Logback configuration file mounted in the container#LOGBACK_CONFIGURATION_FILE=### Additional JVM options, for example the heap size#JAVA_OPTS=-Xmx4g
5. Start Karnak
Run the commands from the folder containing docker-compose.yml:
Action
Command
Download or update the images
docker compose pull
Start in the background
docker compose up -d
Follow the logs
docker compose logs -f
Stop
docker compose down
Stop and delete the data (reset the database)
docker compose down -v
On the first start, Karnak creates the database schema automatically. The database schema is also migrated automatically when you upgrade to a newer image.
Once started, with the configuration above:
Web portal: http://localhost:8080, login admin with the password stored in secrets/karnak_login_password
DICOM listener: AE Title KARNAK-GATEWAY, port 11112 on the Docker host
SSL or STARTTLS to enable authentication. Leave empty for a server without authentication.
MAIL_SMTP_USER / MAIL_SMTP_SECRET
Credentials used when MAIL_SMTP_TYPE is set
Identity provider
By default, Karnak uses the built-in administrator account. Set IDP=oidc to delegate the authentication to an OpenID Connect provider such as Keycloak, and configure it with OIDC_CLIENT_ID, OIDC_CLIENT_SECRET (or OIDC_CLIENT_SECRET_FILE) and OIDC_ISSUER_URI.
Karnak reads the user’s roles from the resource_access.karnak.roles claim of the access token, so the provider must be configured to put the roles of the karnak client in the access token (in Keycloak: client scope roles, with the Add to access token mapper enabled). Three roles are recognized; any other role is ignored and logged as a warning:
Accepted and mapped, but currently gives access to no page; reserved for future use
The Authentication Config page is unrelated to the portal login: it stores OAuth 2.0 credentials for the API calls made by profiles.
Other services
Variable
Default
Description
OCR_URL
http://localhost:8000
Base URL of the de-identification image API used by the automatic mask generation and by the burned-in text check of the conformance report. Deploy this service separately (its repository provides a Docker Compose file) and point OCR_URL to it, for example http://image-ocr-identifier:8000 when it runs on the same Docker network.
The web portal can be served behind a reverse proxy (HTTPS termination). Karnak honors the X-Forwarded-* headers, so make sure the proxy forwards them (X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-For) and that WebSocket connections are allowed, as the user interface relies on server push.
Upgrade
Back up the database volume (docker compose exec karnak-db pg_dump -U karnak karnak > karnak.sql) and the secrets/ folder.
Change the image tag in docker-compose.yml to the new release.
Run docker compose pull then docker compose up -d. The database schema is migrated automatically at startup.
Build the image yourself
To build the image from the source code instead of using Docker Hub, run from the root of the repository:
# Full build inside Docker (requires no local JDK)docker build -t local/karnak:latest -f Dockerfile .
# Or build the application first, then package itmvn clean install -P production
docker build -t local/karnak:latest -f src/main/docker/Dockerfile .
Then replace image: nroduit/karnak:v2.0.0 by image: local/karnak:latest in docker-compose.yml.
Inject your own Logback configuration to control log levels, format, rotation and where Karnak writes application and DICOM transfer logs.
Subsections of Installation
Configure Karnak Logging with Logback
Karnak offers the possibility of injecting your own log configuration to customize how logs are generated and stored.
The logging system used by Karnak is Logback. For detailed information about Logback configuration, see the official manual.
Available Variables
The following variables can be used in your custom Logback configuration file to capture specific de-identification context:
Variable
Description
issuerOfPatientID
Issuer of patient ID before de-identification
PatientID
Patient ID before de-identification
SOPInstanceUID
SOP Instance UID before de-identification
DeidentifySOPInstanceUID
SOP Instance UID after de-identification
SeriesInstanceUID
Series Instance UID before de-identification
DeidentifySeriesInstanceUID
Series Instance UID after de-identification
TagMorphingSOPInstanceUID
SOP Instance UID after tag morphing
TagMorphingSeriesInstanceUID
Series Instance UID after tag morphing
ProjectName
Project name used for de-identification or tag morphing
ProfileName
Profile name used for de-identification or tag morphing
ProfileCodenames
Concatenated list of profile items applied during de-identification or tag morphing
These variables are set (as MDC values, used with %X{name} in a pattern) while a profile is applied to an instance; the Deidentify* / TagMorphing* values, ProjectName, ProfileName and ProfileCodenames are available on the DEBUG event emitted with the CLINICAL marker once the profile has been applied. See the clinical logs example below.
Inject the Logback Configuration File
Using Docker
Set the environment variable LOGBACK_CONFIGURATION_FILE with the path to your Logback configuration file. This will override the default log configuration.
Example:
If you create a custom configuration file named my-logback.xml in the same directory as your docker-compose.yml:
Create a volume to mount the file inside the Karnak container
Define the LOGBACK_CONFIGURATION_FILE environment variable with the container path
Logs everything at the INFO level to the console (standard output) only; no log file is written, so no writable volume is needed and the Docker logs can be read with docker logs or collected by your log driver
Log levels can be adjusted with the standard Spring logging.level.* properties (e.g. the LOGGING_LEVEL_ORG_KARNAK=DEBUG environment variable)
Portable distribution
Logs the org.karnak packages at the INFO level to the console and to the rolling file logs/karnak.log in the extracted directory
The rotation is controlled by the KARNAK_LOGS_MAX_FILE_SIZE (default 50MB), KARNAK_LOGS_MIN_INDEX (default 1) and KARNAK_LOGS_MAX_INDEX (default 10) variables set in run.cfg
Clinical logs
Each de-identification or tag-morphing operation emits a DEBUG event carrying the CLINICAL marker and the variables above. The default configuration does not write these events to a dedicated file. To keep an audit trail, add an appender filtered on the marker to your custom configuration and enable the DEBUG level for the org.karnak package:
<configuration><appendername="CLINICAL_FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/clinical.log</file><rollingPolicyclass="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"><fileNamePattern>logs/clinical_%i.log</fileNamePattern><minIndex>1</minIndex><maxIndex>10</maxIndex></rollingPolicy><triggeringPolicyclass="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><maxFileSize>50MB</maxFileSize></triggeringPolicy><!-- Keep only the events carrying the CLINICAL marker --><filterclass="ch.qos.logback.core.filter.EvaluatorFilter"><evaluatorclass="ch.qos.logback.classic.boolex.OnMarkerEvaluator"><marker>CLINICAL</marker></evaluator><onMismatch>DENY</onMismatch><onMatch>NEUTRAL</onMatch></filter><encoder><pattern>%d SOPInstanceUID_OLD=%X{SOPInstanceUID} SOPInstanceUID_NEW=%X{DeidentifySOPInstanceUID} SeriesInstanceUID_OLD=%X{SeriesInstanceUID} SeriesInstanceUID_NEW=%X{DeidentifySeriesInstanceUID} ProjectName=%X{ProjectName} ProfileName=%X{ProfileName} ProfileCodenames=%X{ProfileCodenames}%n</pattern></encoder></appender><appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender"><!-- Exclude the clinical events from the console --><filterclass="ch.qos.logback.core.filter.EvaluatorFilter"><evaluatorclass="ch.qos.logback.classic.boolex.OnMarkerEvaluator"><marker>CLINICAL</marker></evaluator><onMismatch>NEUTRAL</onMismatch><onMatch>DENY</onMatch></filter><encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><loggername="org.karnak"level="DEBUG"/><rootlevel="INFO"><appender-refref="STDOUT"/><appender-refref="CLINICAL_FILE"/></root></configuration>
With Docker, mount a writable volume for the logs folder (the image runs as a non-root user, so mount it at a path such as /tmp/logs or make the target directory writable).
Info
Clinical logs provide detailed tracking of the de-identification process, including which profiles were applied and how patient data was transformed. This is useful for auditing and compliance purposes.