The Docker-Compose File Explained¶
A short overview over the docker compose file.
The Services¶
The file contains multiple services, 7 are required while the rest are optional.
The ui and backend service are the QHAna UI and the QHAna backend respectively.
The UI is a web frontend written in Angular.
The backend stores all experiment data and is written in ballerina.
The plugins use 5 services in total in the default configuration.
The qhana-plugin-runner service serves the REST APIs of the plugins, while the worker perferms the background computation tasks.
Redis is used as the task queue service and data is stored in the postgres database.
Data is also stored in the minio instance per default (requires the minio plugin to be loaded).
The muse-db service is optional and provides the muse data in a live mysql database.
Warning
The services qhana-plugin-runner and worker must have the exact same configuration apart from the concurrency settings.
They must have the same plugins loaded, use the same task broker for the task queue and use the same database.
Warning
If minio is NOT configured as the default store, then the qhana-plugin-runner and worker services require a shared file system!
This can be achieved by mounting the same docker volume in both containers under the path /app/instance.
services:
qhana-plugin-runner:
platform: linux/amd64
image: ghcr.io/ust-quantil/qhana-plugin-runner:main
depends_on:
- redis
- postgres
redis:
image: "redis:latest"
postgres:
image: "postgres:latest"
minio:
image: quay.io/minio/minio
muse-db:
image: "muse-db"
profiles:
- with_db
worker:
platform: linux/amd64
image: ghcr.io/ust-quantil/qhana-plugin-runner:main
depends_on:
- qhana-plugin-runner
backend:
image: ghcr.io/ust-quantil/qhana-backend:main
ui:
image: ghcr.io/ust-quantil/qhana-ui:main
Open Ports¶
services:
qhana-plugin-runner:
ports:
- "5005:8080"
redis:
ports:
- "6379:6379"
postgres:
ports:
- "5432:5432"
minio:
ports:
- "9000:9000"
- "9001:9001"
muse-db:
worker:
backend:
ports:
- 9091:9090
ui:
ports:
- 8080:8080
The docker-compose file maps 4 ports to the outside.
The user interface is available at port 8080.
This port can be changed to any other open port.
The QHAna UI expects the backend to answer at port 9090.
If the backend port is changed in the docker-compose file, then the port must be changed in the settings page of the QHAna UI for each client using the UI.
The plugin runner api service is exposed under port 5005 by default.
The addresses of all known plugin runners can be edited in the settings page of the QHAna UI.
This has to be done only once as the list is stored in the QHAna backend.
The exposed redis port is for debugging purposes.
Minio ports are exposed and can be used to inspect data in the minio store.
Other plugin runners can use the redis broker for their task queue only if they also use the same database and data store.
All other services are only available in the internal network created by docker-compose (using the service name as hostname).
They are used internally by the plugin runner.
Tip
All URLs that get processed by any part of the QHAna application (e.g., UI, Backend, Plugin Runner (+ Worker), Plugin Registry (+ Worker), minio) need to resolve correctly from inside and outside of the docker compose network.
To achieve this for a development setup without the use of static IPs, a reverse proxy is included in all relevant containers that bounces back calls to the container localhost to the host of the containers.
The reverse proxy itself relys on host.docker.internal to resolve the container host!
For debugging and convenience, other connections (e.g., connections to redis/postgres) also use host.docker.internal and the target containers expose their ports to the local host.
This allows for easy debugging of the database from outside and reusing the redis broker for other celery workers.
Settings¶
services:
qhana-plugin-runner:
environment:
WAIT_HOSTS: host.docker.internal:6379, host.docker.internal:5432
WAIT_SLEEP_INTERVAL: 5
WAIT_TIMEOUT: 600
CONCURRENCY: 2
BROKER_URL: redis://host.docker.internal:6379
RESULT_BACKEND: redis://host.docker.internal:6379
CELERY_QUEUE: "qhana_queue1"
SQLALCHEMY_DATABASE_URI: "postgresql+psycopg2://user:password@host.docker.internal:5432/default_db"
DEFAULT_FILE_STORE: "minio"
MINIO_CLIENT: '{"endpoint": "localhost:9000", "access_key": "QHANA", "secret_key": "QHANAQHANA", "secure": false}'
LOCALHOST_PROXY_PORTS: &localhost-proxy-ports ":5005 :5006 :5007 :9000 :9001 :9091 ${EXTRA_PROXY_PORTS}"
GIT_PLUGINS: "git+https://github.com/UST-QuAntiL/qhana-plugin-runner.git@main#subdirectory=/plugins"
PLUGIN_FOLDERS: &plugin-folders ./git-plugins:./git-plugins/classical_ml/data_preparation
NISQ_ANALYZER_UI_URL: http://localhost:5009
redis:
postgres:
environment:
POSTGRES_PASSWORD: password
POSTGRES_USER: user
POSTGRES_DB: default_db
minio:
environment:
MINIO_ROOT_USER: QHANA
MINIO_ROOT_PASSWORD: QHANAQHANA
worker:
environment:
WAIT_HOSTS: host.docker.internal:6379, host.docker.internal:5432
WAIT_SLEEP_INTERVAL: 5
WAIT_TIMEOUT: 600
CONTAINER_MODE: worker
CONCURRENCY: 2
BROKER_URL: redis://host.docker.internal:6379
RESULT_BACKEND: redis://host.docker.internal:6379
CELERY_QUEUE: "qhana_queue1"
SQLALCHEMY_DATABASE_URI: "postgresql+psycopg2://user:password@host.docker.internal:5432/default_db"
DEFAULT_FILE_STORE: "minio"
MINIO_CLIENT: '{"endpoint": "localhost:9000", "access_key": "QHANA", "secret_key": "QHANAQHANA", "secure": false}'
LOCALHOST_PROXY_PORTS: *localhost-proxy-ports
GIT_PLUGINS: *git-plugins
PLUGIN_FOLDERS: *plugin-folders
backend:
environment:
LOCALHOST_PROXY_PORTS: *localhost-proxy-ports
QHANA_HOST: http://localhost:9091
The QHAna plugin runner and worker share mostly the same configuration.
The variables BROKER_URL, RESULT_BACKEND, SQLALCHEMY_DATABASE_URI, and GIT_PLUGINS must be configured to the same values!
The environent variable for CONCURRENCY can have different values.
The worker container must have the environment variable CONTAINER_MODE set to worker to start the image as a worker (since the exact same image as the plugin runner is used as API server and as worker).
The user and password set in the redis service must be used to construct the SQLALCHEMY_DATABASE_URI (replace the existing user:password section with the changed user and password).
The variable MINIO_CLIENT is a json object that holds the client configuration used to access the minio server.
The containers that use (or can use) a database are configured with a wait script. See https://github.com/ufoscout/docker-compose-wait?tab=readme-ov-file#additional-configuration-options for config options.
Advanced Configuration¶
The plugin runner and worker and the QHAna backend can be configured by mapping a configuration file into the container.
The configuration file must be loaded under /app/instance/config.[json|toml] for the plugin runner and worker.
The backend expects the configuration under /app/data/Config.toml but all configurations can be set via env variables documented in the (README.md)[https://github.com/UST-QuAntiL/qhana-backend#configuration-handling].
For the information of what config options are available in the config files please refer to https://github.com/UST-QuAntiL/qhana-plugin-runner and https://github.com/UST-QuAntiL/qhana-backend/blob/main/Config-template.toml.
Volumes¶
The docker compose file configures three volumes for persistent data storage.
The plugin runner and worker container share the same volume named instance that is used as a shared file system.
All data generated by the plugin runner is saved to that shared file system or the configured minio instance (which has its own volume).
To achieve true persistence the redis and postgres services need to be configured with volumes too (this is only needed for production setups).
Please refer to the image documentations on docker hub for how these volumes should be configured.
The volume named experiments is used by the QHAna backend to persist the backend data.
It contains all data stored for the experiments (as files in the file system) and an sqlite database.
The backend can also be configured to use a MariaDB database with the Config.toml file.
services:
qhana-plugin-runner:
volumes:
- instance:/app/instance
minio:
volumes:
- minio:/data
worker:
volumes:
- instance:/app/instance
backend:
volumes:
- experiments:/app/data
volumes:
minio:
instance:
experiments:
Hint
As a basic security measure all QHAna containers run as an unpriviledged user. This can lead to problems with mounting folders into the same space as the volumes. This stack overflow question might be a good starting point for researching a solution: https://stackoverflow.com/questions/40462189/docker-compose-set-user-and-group-on-mounted-volume
The QHAna backend uses the user ballerina while the plugin runner containers use gunicorn as the username.
For completeness: the QHAna UI runs as the user nginx.
![digraph compose {
rankdir=LR;
edge [minlen=2];
browser [shape=rect]
subgraph cluster_localhost {
style=filled;
color=lightgrey;
node [style=filled,color=white,fillcolor=white,shape=rect];
uiPort [label=8080]
qhanaPluginRunnerPort [label=5005]
registryPort [label=5006]
redisPort [label=6379]
backendPort [label=9091]
postgresPort [label=5432]
postgresRegistryPort [label=5433]
minioPortA [label=9000]
minioPortB [label=9001]
label = "Localhost";
}
subgraph cluster_compose {
style=filled;
color=lightgrey;
node [style=filled,color=black,fillcolor=white,shape=rect];
edge [dir=none,minlen=3];
uiPortD [label=8080,color=white]
ui [width=2.3]
uiPort -> uiPortD -> ui
subgraph cluster_pluginRunner {
style=filled;
color=grey;
fillcolor=lightgrey;
node [style=filled,color=black,fillcolor=white,shape=rect];
edge [dir=none,minlen=0,penwidth=4];
qhanaPluginRunner [label="qhana-plugin-runner",width=2.3]
worker [width=2.3]
qhanaPluginRunner -> worker
}
qhanaPluginRunnerPortD [label=8080,color=white]
qhanaPluginRunnerPort -> qhanaPluginRunnerPortD -> qhanaPluginRunner
workerPortD [label="–",color=white]
workerPortD -> worker
subgraph cluster_Registry {
style=filled;
color=grey;
fillcolor=lightgrey;
node [style=filled,color=black,fillcolor=white,shape=rect];
edge [dir=none,minlen=0,penwidth=4];
registry [width=2.3]
registryWorker [width=2.3]
registry -> registryWorker
}
registryPortD [label=8080,color=white]
registryPort -> registryPortD -> registry
registryWorkerPortD [label="–",color=white]
registryWorkerPortD -> registryWorker
backendPortD [label=9090,color=white]
backend [width=2.3]
backendPort -> backendPortD -> backend
redisPortD [label=6379,color=white]
redis [width=2.3]
redisPort -> redisPortD -> redis
postgresPortD [label=5432,color=white]
postgres [width=2.3]
postgresPort -> postgresPortD -> postgres
postgresRegistryPortD [label=5432,color=white]
postgresRegistry [width=2.3]
postgresRegistryPort -> postgresRegistryPortD -> postgresRegistry
minioPortAD [label=9000,color=white]
minioPortBD [label=9001,color=white]
minio [width=2.3,height=1]
minioPortA -> minioPortAD -> minio
minioPortB -> minioPortBD -> minio
label = "Docker Compose";
subgraph cluster_dns {
style=filled;
color=lightgrey;
fillcolor=gray;
node [style=filled,color=white,fillcolor=white,shape=rect,width=1.8];
redisPortDNS [label="6379\n(redis)"]
postgresPortDNS [label="5432\n(postgres)"]
postgresRegistryPortDNS [label="5433\n(postgres reg.)"]
label = "host.docker.internal";
}
subgraph cluster_proxy {
style=filled;
color=lightgrey;
fillcolor=gray;
node [style=filled,color=white,fillcolor=white,shape=rect,width=1.8];
qhanaPluginRunnerPortP [label="5005\n(plugin runner)"]
registryPortP [label="5006\n(registry)"]
unusedAPortP [label="5007\n(free)"]
backendPortP [label="9091\n(backend)"]
minioPortAP [label="9000\n(minio)"]
minioPortBP [label="9001\n(minio)"]
label = "Localhost (proxy)";
}
}
browser -> uiPort
browser -> qhanaPluginRunnerPort
browser -> registryPort
browser -> minioPortA
browser -> minioPortB
qhanaPluginRunner -> redisPortDNS [color=blue]
worker -> redisPortDNS [color=blue]
worker -> qhanaPluginRunnerPortP
worker -> registryPortP
worker -> backendPortP
worker -> minioPortAP
registry -> redisPortDNS [color=blue]
registryWorker -> redisPortDNS [color=blue]
registryWorker -> qhanaPluginRunnerPortP
registryWorker -> backendPortP
qhanaPluginRunner -> postgresPortDNS [color=blue]
worker -> postgresPortDNS [color=blue]
registry -> postgresRegistryPortDNS [color=blue]
registryWorker -> postgresRegistryPortDNS [color=blue]
}](_images/graphviz-d805b1c87056ddf67bd18e0b9b43b132cd785f1f.png)