Skip to main content

Starting a QA-Board server

Set up a QA-Board server for your team: by the end, your users sign in to the web app, and qa saves their results to it. This page uses Docker Compose on one Linux host. On Kubernetes, follow Running QA-Board on Kubernetes instead.

Try it first

The quickstart starts QA-Board on your computer with one Compose file, and demo projects to look at.

The quickstart: start one Compose file, and once every service is healthy, QA-Board answers on localhost:5151 with the demo projects.

What you need​

  • A Linux host with Docker Engine, docker compose 2.24 or newer (docker compose version), git and python3. For its size: deployments/production.env runs 2 backend replicas of up to 8 uwsgi processes each, the Helm chart asks for 1 CPU and 4 GiB of memory per replica, and services/db/qaboard.conf has PostgreSQL settings for an 8-core, 32 GB host.
  • Disk on the host, in Docker's volumes and images (/var/lib/docker by default): QA-Board's images (deploy.py keeps those of the last 3 versions), the database, the clones of your git repositories, and caches: decoded images, up to 20 GB (QABOARD_IMAGE_CACHE_GB), and the image server's tiles, kept 5 days.
  • The results storage: a folder that the server and every machine that runs qa (CI runners, workstations, cluster nodes) mount at the same path, e.g. an NFS share at /mnt/qaboard. qa writes the output files there itself, and only sends their path to the server, which reads them there.
  • A hostname that your users, CI runners and git hosts reach, e.g. qaboard.example.com, and a TLS certificate for HTTPS.
  • One port: the web app, the API and the webhooks answer on port 5151 (QABOARD_PORT_HTTP), or 443 with HTTPS.
The admin services stay on the host

docker-compose.yml also publishes pgAdmin (5050), RabbitMQ (5672, its management UI on 15672) and Flower (8888), on the host itself only (127.0.0.1): from another computer, use an ssh tunnel. RabbitMQ has a guest:guest account until you set one, and anyone who reaches it can run commands on Celery workers: see passwords and admin services before you publish it for workers on other hosts.

1. Get QA-Board​

On the host:

git clone https://github.com/Samsung/qaboard.git
cd qaboard

You deploy from this checkout: it has the compose files, nginx's and PostgreSQL's configuration, and deployments/deploy.py. The code comes from the images. To deploy a release, check out its tag (git checkout v<version>), so that both match.

2. Create the results storage​

To start on a single host, a local folder works. Share it later:

sudo mkdir -p /mnt/qaboard
sudo chmod 777 /mnt/qaboard

If your storage is a share, mount it on the host at the path the runners use. To use another path than /mnt/qaboard, use it in the overlay below, list it in QABOARD_STORAGE_ROOTS, and set storage in your projects' qaboard.yaml (see where is the data saved).

Shared storage

Read how to set up NFS or Samba, and the options for NFS volumes in compose files. In the cloud, use a file storage like AWS EFS or GCP Filestore. Object storage, like S3, is on the roadmap.

3. Describe your site​

A site overlay adds your volumes, settings and nginx configuration to docker-compose.yml, without changing it. Create one in a folder for your site, starting from deployments/example.yml:

deployments/mysite/mysite.yml
services:
backend:
volumes:
# The results storage, at the same path as on the runners
- /mnt/qaboard:/mnt/qaboard
environment:
# The address users open: links in emails, single sign-on redirects
- QABOARD_URL=http://qaboard.example.com:5151
# Your user name: you're an admin once you sign in (step 6)
- QABOARD_ADMINS=alice
proxy:
volumes:
# nginx serves /s/<path> from /var/qaboard/<path>
- /mnt/qaboard:/var/qaboard/mnt/qaboard:ro
cantaloupe:
volumes:
# The image server reads /iiif/2/<path> from /repository/<path>: each storage root, read-only
- /mnt/qaboard:/repository/mnt/qaboard:ro
  • The storage is mounted at the same path in the backend, and under /var/qaboard/ in nginx, which serves it at /s/: browsers that open a folder there get QA-Board's file browser. The image server (Cantaloupe) sees only what you mount in it, at the same paths as in nginx.
Upgrading

docker-compose.yml used to mount the host's whole filesystem in the image server. If the image viewers show no images after you upgrade, mount your storage in cantaloupe like above.

  • QABOARD_URL: use https:// only once HTTPS works (below): session cookies are then only sent over HTTPS.
  • Other settings go in services.backend.environment too: see environment variables.

4. Write the env file and the secrets​

deploy.py reads an env file per environment: the compose files, the compose project, and the settings docker compose reads itself. Copy deployments/production.env and add your overlay:

deployments/mysite/production.env
COMPOSE_PROJECT_NAME=qaboard
COMPOSE_FILE=docker-compose.yml:production.yml:deployments/mysite/mysite.yml
QABOARD_BACKEND_REPLICAS=2
QABOARD_UWSGI_PROCESSES=8
QABOARD_UWSGI_CHEAPER=4
QABOARD_UWSGI_CHEAPER_INITIAL=8
# QABOARD_PORT_HTTP=5151

Put the secrets in .env, at the root of the checkout (chmod 600 .env). Generate them with openssl rand -base64 32:

.env
# Push webhooks must send it (step 8)
QABOARD_WEBHOOK_SECRET=...
# Signs sessions. Generated and kept in the backend's data volume if unset: set it to move hosts without signing everyone out
# SECRET_KEY=...
# The database, and the admin services (see below)
QABOARD_DB_PASSWORD=...
PGADMIN_DEFAULT_EMAIL=admin@example.com
PGADMIN_DEFAULT_PASSWORD=...
# Only if Celery workers connect to RabbitMQ (QABOARD_TUNING_RUNNER=celery). URL-safe characters
# QABOARD_RABBITMQ_USER=qaboard
# QABOARD_RABBITMQ_PASSWORD=...
# Optional: Flower (the task queue's UI) also asks for this login. At /flower/, only QA-Board's admins get it
# FLOWER_BASIC_AUTH=admin:...
Where each setting goes
  • .env: secrets. The backend gets all its variables.
  • The env file: what docker compose reads itself: COMPOSE_*, QABOARD_PORT_HTTP, QABOARD_BACKEND_REPLICAS, QABOARD_IMAGE... deploy.py gives it to docker compose, which then doesn't read .env for those.
  • The overlay's services.backend.environment: the other settings. docker-compose.yml sets QABOARD_LOGIN_TYPE, QABOARD_LOGIN_REQUIRED, QABOARD_LOGIN_RESTRICTED, QABOARD_LOGIN_RESTRICTED_YAML, QABOARD_ADMINS and QABOARD_INGEST_AUTH from the env file, which wins over .env: set them in the overlay or in the env file, never in .env.

Passwords and admin services​

deploy.py passes these passwords from .env to docker compose, for the ${...} of the compose files.

  • The database is created with QABOARD_DB_PASSWORD (default: password), and the backend and the backups use it. To change the password of an existing database, see database password.
  • RabbitMQ is created with QABOARD_RABBITMQ_USER and QABOARD_RABBITMQ_PASSWORD (default: guest:guest). Celery workers run the commands of the tasks they get: anyone who can connect to RabbitMQ can run commands on them. For an existing RabbitMQ, add the user with docker compose exec rabbitmq rabbitmqctl add_user and set_permissions, or change the password with rabbitmqctl change_password. For workers on other hosts, publish it with QABOARD_RABBITMQ_HOST_IP=0.0.0.0 in the env file, and give each team its own account (sharing the broker).
  • The admin services are only published on the host itself (127.0.0.1): RabbitMQ (QABOARD_PORT_RABBITMQ, 5672, and its management UI on QABOARD_PORT_RABBITMQ_MNGT, 15672), Flower (QABOARD_PORT_FLOWER, 8888) and pgAdmin (PGADMIN_PORT, 5050). nginx serves Flower at /flower/ to QA-Board's admins only.

5. Deploy​

# The first time: --init allows a new compose project, with an empty database
deployments/deploy.py deployments/mysite/production.env up --version <version> --init

<version> is a release, like 1.2.0. deploy.py pulls its images (ghcr.io/samsung/qaboard:<service>-<version>), runs the database migrations, starts the services, and records the version, so that rollback can come back to it. Without --version, it deploys the latest images of master; with --build, it builds the images from your checkout. Plain docker compose --env-file deployments/mysite/production.env up -d works too, without zero-downtime upgrades.

Check it worked:

deployments/deploy.py deployments/mysite/production.env status
curl -fsS http://localhost:5151/api/v1/health

status lists the containers: all running, and the backend replicas healthy. The health check answers "status": "ok" once the backend reaches its database. Open http://<your-host>:5151: the web app opens, with no projects yet. If something is wrong, see Troubleshooting.

6. Make yourself an admin​

Admins see Admin in the user menu: users, sign-in, settings, and the jobs that clean the storage.

  • The user names in QABOARD_ADMINS (step 3) are admins as soon as they sign in.

  • To sign in before LDAP or single sign-on work, create a local admin account. It asks for the password:

    deployments/deploy.py deployments/mysite/production.env compose exec backend qaboard-admin create-admin admin --password

Check it worked: sign in with Sign in, at the top right, and open Admin. Its Overview has a security checklist of what to look at next. More in User management.

7. Choose how users sign in​

By default, users have local accounts, and anyone can create one from the sign-in page (Create one). Instead:

Then decide who gets an account, and whether anonymous users see anything.

To apply changes to .env or to the overlay, deploy again: deploy.py <env file> up recreates the backend with them, without downtime. Check it worked: qaboard-admin check-sign-in in the backend's container, or Admin → Sign-in → Test, then sign in.

8. Connect your git hosts​

QA-Board reads your repositories for committers' avatars and each pushed commit's qaboard.yaml, and learns about pushes from webhooks.

  1. Tokens: add them to .env, e.g. GITHUB_ACCESS_TOKEN, or GITLAB_HOST and GITLAB_ACCESS_TOKEN. For other hosts, or several, see Git hosts.
  2. Webhooks: in each repository, send push events to <QABOARD_URL>/webhook/github (or gitlab, gitea, bitbucket), with your QABOARD_WEBHOOK_SECRET: see Connect your git host.

Check it worked: push a commit. The git host shows that the webhook's latest delivery got 200.

9. Save the first results​

On a machine that mounts the results storage at the same path as the server, e.g. a CI runner, install qa and point it to the server:

pip install qaboard
export QABOARD_URL=http://qaboard.example.com:5151

In a project wrapped with qa, qa --share batch <batch> saves the results and prints a link to them: Your project on QA-Board walks through it. Check it worked: the link opens the commit page with your runs and their output files.

10. Secure the server​

Before you open it to your team:

  • Serve it over HTTPS (below), with QABOARD_URL=https://....
  • Require tokens to send results: by default, anyone who can reach the server can send results. Move ingest.auth from off to warn to token once your CI jobs have tokens (results sent by qa).
  • Set QABOARD_WEBHOOK_SECRET (step 4), and the same secret in your webhooks: without it, anyone can send fake push events.
  • Turn off local sign-up (auth.local_signup) if only admins create accounts, and require signing in if anonymous users shouldn't see results.
  • Set the passwords of the database and the admin services (step 4), and only expose QA-Board to networks you trust.
  • Sandbox the runs' HTML files: by default, the HTML and SVG files of runs run their scripts as whoever opens them. Set QABOARD_SANDBOX_HTML=true once your users know (HTML reports).

The security checklist in Admin → Overview checks the sign-in, token and HTTPS settings. SECURITY.md lists the known issues and their mitigations.

HTTPS and reverse proxies​

Use your own copy of services/nginx/conf.d/qaboard.conf in the site overlay (keep its include snippets/... lines: they serve the files and the file browser). Files mounted under /tmp/deployment/nginx/conf.d/ in the proxy container are added to (or replace) services/nginx/conf.d/:

deployments/mysite/nginx/qaboard.conf (copied from services/nginx/conf.d/qaboard.conf)
server {
# ...
listen 443 ssl;
ssl_certificate_key /ssl/cert.key;
ssl_certificate /ssl/cert.pem;
# ...
}

Mount it, and the key and certificate:

deployments/mysite/mysite.yml
services:
proxy:
ports:
- "443:443"
volumes:
- "./deployments/mysite/nginx/qaboard.conf:/tmp/deployment/nginx/conf.d/qaboard.conf:ro"
- "somewhere/cert.key:/ssl/cert.key"
- "somewhere/cert.pem:/ssl/cert.pem"

Then set QABOARD_URL=https://..., and deploy again.

Getting a certificate from your IT

Generate a key and a certificate signing request (CSR), and send the CSR to your IT:

openssl genrsa -out cert.key 2048
openssl req -new -sha256 -key cert.key -out cert.csr

If they send back a .cer certificate in the DER format, convert it to PEM: openssl x509 -in cert.cer -inform der -outform pem -out cert.pem.

To use your own reverse proxy instead, e.g. nginx, set QABOARD_PORT_HTTP=8080 in the env file and the config below. QA-Board's nginx passes the X-Forwarded-Proto and X-Forwarded-Host headers your proxy sets to the backend (the single sign-on redirects need them), else its own:

server {
listen 80;
server_name default_server;
location / {
proxy_pass http://localhost:8080/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
}
}

Behind your proxy, a load balancer or an ingress, set QABOARD_TRUSTED_PROXY_HOPS=1, so that the audit log records users' IPs (see rate limits and proxies).

Run it day to day​

  • Upgrade: read the release notes ("Before you upgrade"), check out the new release, then deploy.py <env file> up --version <version>. If it goes wrong, rollback: see Upgrades and rollbacks.
  • Back up the database, and test a restore: see Backups.
  • Free disk space in the results storage: see Cleanup.
  • Follow errors in Sentry (monitoring), and the runs stuck as running in Runs activity.

Backups​

The cron-backup-db service in production.yml makes daily backups of the database to /backups, and keeps those of the last 14 days (QABOARD_BACKUP_RETENTION_DAYS). Mount a host directory there, in the db service too (restore-db reads the backups from it), and copy the backups off the host. Without it, the backups fail.

deployments/mysite/mysite.yml
services:
cron-backup-db:
volumes:
- /srv/qaboard/backups:/backups
db:
volumes:
- /srv/qaboard/backups:/backups

To restore one: deployments/deploy.py <env file> restore-db <file>.dump. See Backups.

Cleanup​

Schedule the cleanup in Admin → Storage: delete old results as each project's qaboard.yaml says, delete for good the runs users deleted, delete old artifacts, with each run's log and errors. See cleaning the storage.

Or use cron jobs on the host:

# Weekly cleanup of old results, on Saturdays
# https://samsung.github.io/qaboard/docs/storage/deleting-old-data
59 1 * * 6 cd qaboard && docker compose --env-file deployments/mysite/production.env exec -T backend qaboard_clean

# Weekly removal of unused docker images, on Sundays, so that they don't fill the disk
59 1 * * 0 docker image prune --force

qaboard_clean --help lists the command's options.

Problems starting a server? Create an issue. For development, read the READMEs of the backend and the frontend.

Environment variables​

Set them in services.backend.environment in your site overlay, and the secrets in .env: see where each setting goes. With the Helm chart, in config and the secret (Kubernetes). Many settings can also be changed in Admin → Settings, without a deploy: see settings.

ENV VariableDefaultUsage
QABOARD_URLhttp://qaboardThe address users open, e.g. https://qaboard.example.com: links in emails, single sign-on redirects. With https://, session cookies are only sent over HTTPS
SECRET_KEYgeneratedSigns the sessions. If unset, a key is generated once and saved in the backend's data volume. With several hosts/kubernetes, set the same value everywhere.
QABOARD_ADMINSnoneUser names of admins, or emails of directory and single sign-on accounts, comma-separated. See user management
QABOARD_GIT_HOSTSnoneYour git hosts (GitHub, GitLab, Gitea/Forgejo, Bitbucket...) with their tokens and webhook secrets: a JSON list, or the path to a JSON/YAML file. See Git hosts
GITLAB_HOSThttps://gitlab.com, if QABOARD_GIT_HOSTS isn't set eitherYour GitLab, e.g. https://gitlab.example.com. It's the default git host: for projects that don't say where they are hosted, and for pushes to /webhook/gitlab from hostnames the server doesn't know. Keep it set when you upgrade, see Git hosts
GITLAB_ACCESS_TOKENnoneToken for GITLAB_HOST: read_repository scope to clone private repositories, read_api to show GitLab CI job statuses and committers' avatars, api to play manual jobs, and to retry the jobs that recreate deleted artifacts
GITHUB_ACCESS_TOKENnoneToken for github.com (always known, even without a token) and the hosts of QABOARD_GITHUB_HOSTS: to clone private repositories, look up committers' avatars and use GitHub Actions (see GitHub integration)
QABOARD_GITLAB_HOSTSnoneComma-separated. Hostnames (e.g. gitlab-alias) are other names of GITLAB_HOST. URLs (e.g. https://gitlab2.example.com) are other GitLab servers, that get GITLAB_ACCESS_TOKEN. QABOARD_GIT_HOSTS lets you give each its own token
QABOARD_GITHUB_HOSTSnoneComma-separated hostnames of GitHub Enterprise servers, that get GITHUB_ACCESS_TOKEN. QABOARD_GIT_HOSTS lets you give each its own token
QABOARD_PROXY_HOSTSnoneComma-separated hostnames the web app may load avatars and badges from through the backend, besides your git hosts (QABOARD_GIT_HOSTS, GITLAB_HOST...), the host of QABOARD_AVATAR_URL, and gravatar. Add the hosts of your projects' project.avatar_url and integrations[].src images
QABOARD_STORAGE_ROOTS/mnt/qaboardFolders where your projects store outputs and artifacts, separated by : (e.g. /mnt/qaboard:/mnt/datasets). The backend refuses results stored elsewhere, and never writes, deletes or runs code outside these folders
QABOARD_SANDBOX_HTMLofftrue: the runs' HTML and SVG files can't act as whoever opens them, their scripts run sandboxed. Set it for the backend and the proxy. See HTML reports
QABOARD_FILES_UNIX_PERMISSIONSnoneFolders, separated by : (e.g. /home), where users only read the files under /s/ that their Unix account can read. See user management
QABOARD_ACCOUNTS_COMMANDnoneCommand printing the users' accounts like /etc/passwd (e.g. ssh host getent passwd), for file owners and Unix permissions, when you can't mount a complete /etc/passwd. See users' accounts
QABOARD_GROUPS_COMMANDnoneSame for the groups, like /etc/group
QABOARD_ACCOUNTS_REFRESH86400How often (seconds) the backend runs these commands
QABOARD_WEBHOOK_SECRETnoneSecret that push webhooks must send (GitLab's Secret token, GitHub's Secret...), unless their host has its own webhook_secret in QABOARD_GIT_HOSTS. Recommended: without it anyone who can reach the server can send fake push events
QABOARD_PORT_HTTP5151Port mapped to the app on the host. Read by docker compose: set it in the env file
QABOARD_DB_HOSTdbConnect the backend to another database host, e.g. a database managed by your IT. Read by docker compose: set it in the env file
QABOARD_DB_PORT5432Connect to a non-default database port. Read by docker compose: set it in the env file
QABOARD_DB_USER / QABOARD_DB_PASSWORD / QABOARD_DB_NAMEqaboard / password / qaboardThe database's credentials and name. Keep the password in .env
QABOARD_DB_POOL_SIZE / QABOARD_DB_MAX_OVERFLOW4 / 2Database connections kept open by each uwsgi process (it runs 4 threads), and extra ones it may open. PostgreSQL's max_connections must cover them, see Database settings
JENKINS_AUTHnoneCredentials used to trigger jenkins jobs on 1 or many jenkins servers. The format is a JSON string looking like {"hostname_1": {"user": "jenkinsuser", "token": "xxxxx", "crumb": "yyy"}} (see Jenkins builds for the token and crumb)
JENKINS_BUILD_TOKENnoneThe authentication token of Trigger builds remotely sent to the Jenkins jobs whose integration has no token. Without it, they get none. Older versions sent qaboard: if your jobs expect it, set JENKINS_BUILD_TOKEN=qaboard
QABOARD_LOGIN_TYPELOCALLOCAL, LDAP or SAML. Single sign-on (QABOARD_OIDC_*) comes on top of any of them. See user management
QABOARD_LOGIN_REQUIREDemptytrue blocks anonymous users, false allows them. Empty: as set in Admin → Settings (auth.require_sign_in)
QABOARD_DISABLE_SIGNUPemptytrue disables the sign-up of local accounts, false enables it. Empty: as set in Admin → Settings (auth.local_signup, on with LOCAL, off with LDAP or SAML)
QABOARD_LDAP_*, QABOARD_SAML_*, QABOARD_OIDC_*, QABOARD_LOGIN_RESTRICTED*...Sign-in: see the sign-in variables and single sign-on
QABOARD_INGEST_AUTH, QABOARD_ACCESS_POLICY...Settings you can also change in Admin → Settings: see settings
QABOARD_TUNING_RUNNERlocalHow tuning/"redo" runs started from the webapp are dispatched: local, celery or lsf
QA_RUNNERS_LSF_BRIDGEnoneWith lsf: the command used to submit LSF jobs, e.g. ssh user@lsf-host 'bsub_su {user} -I {bsub_command}'
QABOARD_DASK_SCHEDULERSnoneComma-separated dask scheduler addresses the backend trusts (to stop dask jobs from the webapp)
QABOARD_IMAGE_SERVERS{"default": "/iiif/2/"}JSON map of file extensions to IIIF image servers, e.g. {"default": "/iiif/2/", "raw,dng": "https://iip.example.com/fcgi-bin/iipsrv.fcgi?IIIF=/"}. Each URL is followed directly by the image's path
QABOARD_IMAGE_CACHE_DIR$QABOARD_DATA_DIR/cache/imagesWhere the images whose pixel values users read in the image viewer, or that they compare with "Where they differ", are kept, decoded (16-bit, float, raw...), so that reading them again is fast, and in regions/ the regions found (removed after a month unused). In videos/: what's in the videos, and where two videos differ (removed after a month). Shared by the backend's workers: use local disk
QABOARD_IMAGE_CACHE_GB20The disk space they can use: the least recently used are removed. Decoded images are large (a 12 MP 16-bit RGB image takes 72 MB)
QABOARD_PATH_MAPPINGS[]JSON list of ["windows_prefix", "linux_prefix"] pairs, used to show Windows paths to results (qa reads it too, from the environment or a site package, to convert Windows paths), e.g. [["\\\\server\\share", "/mnt/share"]]
QABOARD_DEFAULT_USERqaboardThe user CI runs are saved under (with {user} in storage). Runs started from the webapp replace it with the logged-in user in output paths
QABOARD_SKIP_METRICS_AGGREGATIONnoneComma-separated project ids, or their start (e.g. group/ for all the projects of a group), whose commit lists don't show the batches' aggregated metrics (their median and average): for projects with so many runs per batch that computing them slows the lists down. Commit pages still show them
QABOARD_DOCS_ROOT/Where the webapp's links to the docs point. By default, the docs served by this server at /docs/, which match its version
QABOARD_AVATAR_URLnoneAvatar URL template with a {user_name} placeholder, e.g. from a company directory: for the logged-in user's menu, and for GitLab committers who didn't upload an avatar. For the user's menu it's only read from the environment, not from a site package
QABOARD_ANALYTICS_ENABLEDtrueRecords how people use the web app and qa, for Admin → Usage. See Usage analytics
POSTHOG_API_KEY / POSTHOG_HOSTnoneAlso sends the web app's events to PostHog
REDIS_HOST / REDIS_PORTredis / 6379 (set in docker-compose.yml)Redis cache shared by the backend replicas. The backend still works if it's unreachable
QABOARD_SUPPORT_URLGitHub issuesWhere users report bugs and send feedback, can be a mailto:
QABOARD_CLI_REQUIREMENTSnoneWhat scripts install besides qaboard to use qa here, comma-separated pip requirements, e.g. your site package. The web app's Script snippets list them (results in scripts).
QABOARD_LOGIN_HINTnoneAdvice for users whose username or password is wrong, shown in the login dialog, e.g. Use your Windows credentials.
QABOARD_SMTP_HOSTnoneSMTP server QA-Board sends emails with, e.g. when batches users started end. Without it, users can't ask for emails. See Emails
QABOARD_SMTP_PORT25, or 587 with starttls, 465 with sslSMTP port
QABOARD_SMTP_SECURITYnonenone, starttls or ssl
QABOARD_SMTP_USER / QABOARD_SMTP_PASSWORDnoneIf the SMTP server needs a login. Keep the password in .env, not in environment
QABOARD_EMAIL_FROMQA-Board <qaboard@$QABOARD_EMAIL_DOMAIN>Sender of the emails, e.g. QA-Board <qaboard-noreply@example.com>
QABOARD_EMAIL_REPLY_TOnoneWhere replies go, e.g. the team that supports QA-Board
QABOARD_EMAIL_DOMAINnoneUsers whose account has no email address get emails at <username>@<domain>, e.g. example.com with LDAP usernames
QABOARD_EMAIL_TIMEZONEUTCTime zone of the times in emails, e.g. Asia/Jerusalem
QABOARD_WEBCDE_INSTALLERnoneRuns whose outputs include a cde.sh get a button to open them in WebCDE, a desktop app. When WebCDE isn't running, users are told to install it from this location
CANTALOUPE_MEM_START1gStarting memory for the image server (set in services.cantaloupe.environment)
CANTALOUPE_MEM_MAX2gMax memory for the image server (set in services.cantaloupe.environment)
QABOARD_BACKEND_REPLICAS1 (2 in deployments/production.env)Backend replicas, see Deployments
QABOARD_UWSGI_PROCESSES16 (8 in deployments/production.env)uwsgi processes per backend replica, in production
SENTRY_DSNnoneReport errors and performance data to Sentry, e.g. https://xxxxxxxxxxx@sentry.io/000. See Monitoring with Sentry for the other settings
SENTRY_TRACES_SAMPLE_RATE0.2Share of the requests that are traced (formerly SENTRY_SAMPLE_RATE)

Emails​

QA-Board can email users when the batches they start end, from the web app or with qa --share batch (what users get). Once the backend has an SMTP server, everyone gets them, except in CI. Users turn them off in the terminal with QA_NOTIFY=none. Configure it in your overlay, and the SMTP password, if any, in .env:

deployments/mysite/mysite.yml
services:
backend:
environment:
- QABOARD_SMTP_HOST=smtp.example.com
- QABOARD_EMAIL_FROM=QA-Board <qaboard-noreply@example.com>
- QABOARD_EMAIL_REPLY_TO=qaboard-support@example.com
# Users without an email address in their account get emails at <username>@example.com
- QABOARD_EMAIL_DOMAIN=example.com
- QABOARD_URL=https://qaboard.example.com
  • The emails are designed for Outlook 2016/2019 as well as recent clients, in HTML with a plain text version. They are marked as automated (Auto-Submitted), so out-of-office replies aren't sent back, and have X-QABoard-Project, X-QABoard-Batch and X-QABoard-Status headers for mail rules.
  • They're sent by the backend when it saves the last run of a batch. If the SMTP server fails, the error is in the backend's logs and in the batch_notifications table, and the run is saved anyway.
  • To check how they look, or test your SMTP settings, run uv run backend/backend/notifications/preview.py: it renders examples, and --send you@example.com emails them to you.
  • Like results, the requests to email users when their qa batch ends come from the CLI without a login: anyone who can reach the server could have QA-Board email a user about a batch. QA-Board only emails the address of the user's account (or <username>@$QABOARD_EMAIL_DOMAIN), and at most once per qa batch.