This guide sets up OpenWhistle with Docker Compose. At the end, the digital channel of your internal reporting office (interne Meldestelle) runs on your own server. The authoritative reference remains the installation guide in the documentation.

Prerequisites

  • A Linux server with 1 vCPU, at least 512 MB RAM (1 GB recommended) and 5 GB of storage
  • Docker 24 or newer with the docker compose plugin (v2)
  • A domain that points to the server, such as meldestelle.ihr-unternehmen.de
  • Ports 80 and 443 open
  • Root or sudo access and some practice in the terminal

Automated instead of by hand

The repository contains an Ansible role under ansible/. It installs Docker, creates a system service and fetches a Let's Encrypt certificate. This guide shows the manual route.

Step 1: Provision the server

Choose a VPS

A small VPS is enough. Choose a provider with a data centre in the EU that offers a data processing agreement under Art. 28 GDPR.

Create a user with sudo rights and log in with an SSH key.

Step 2: Install Docker

Docker Engine and the Compose plugin

Follow Docker's official installation guide for your distribution. Then check both versions:

docker --version          # 24.0 or newer
docker compose version    # v2.x

Step 3: Configure OpenWhistle

Clone the repository and create .env

Clone the repository and copy the example configuration:

git clone https://github.com/openwhistle/OpenWhistle.git
cd OpenWhistle
cp .env.example .env

Enter at least these values in .env. openssl rand -hex 32 generates random keys.

# Production stack with nginx and TLS for every docker-compose command
COMPOSE_FILE=docker-compose.prod.yml

# At least 32 characters
SECRET_KEY=<openssl rand -hex 32>
# Recommended: a separate key for encrypting the reports
ENCRYPTION_KEY=<openssl rand -hex 32>

# Passwords for the database and Redis, the same ones in the URLs
POSTGRES_PASSWORD=starkes-passwort
REDIS_PASSWORD=zweites-starkes-passwort
DATABASE_URL=postgresql+asyncpg://openwhistle:starkes-passwort@db:5432/openwhistle
REDIS_URL=redis://:zweites-starkes-passwort@redis:6379/0

# Your domain, for links in emails and the fallback certificate
APP_PUBLIC_URL=https://meldestelle.ihr-unternehmen.de
TLS_HOSTNAME=meldestelle.ihr-unternehmen.de

# Optional: email to the reporting office and deadline reminders
# NOTIFY_EMAIL_ENABLED=true
# NOTIFY_EMAIL_TO=meldestelle@ihr-unternehmen.de
# NOTIFY_SMTP_HOST=smtp.ihr-mailserver.de
# NOTIFY_SMTP_PORT=587
# NOTIFY_SMTP_USER=meldestelle@ihr-unternehmen.de
# NOTIFY_SMTP_PASSWORD=mail-passwort
# REMINDER_ENABLED=true

Back up the key

ENCRYPTION_KEY encrypts the reports; without it, SECRET_KEY does. If the key is lost, the reports can no longer be read. Keep it in a password manager or a secret store.

All other settings are in the configuration table.

Step 4: Install the HTTPS certificate

A Let's Encrypt certificate in nginx/certs/

The stack serves HTTPS from the start. Without your own certificate, the tls-init service creates a self-signed one for TLS_HOSTNAME. That is enough for testing, not for production.

Fetch a certificate while port 80 is still free, and copy it. A symlink to /etc/letsencrypt/live points to nothing inside the container.

sudo certbot certonly --standalone -d meldestelle.ihr-unternehmen.de
sudo cp /etc/letsencrypt/live/meldestelle.ihr-unternehmen.de/fullchain.pem nginx/certs/
sudo cp /etc/letsencrypt/live/meldestelle.ihr-unternehmen.de/privkey.pem nginx/certs/
sudo chmod 600 nginx/certs/privkey.pem

To renew, copy the new files and run docker compose up -d tls-init nginx. Restarting nginx alone keeps the old certificate.

Step 5: Start OpenWhistle

Start all services

One command starts the app, PostgreSQL, Redis and nginx. The database migrations run automatically at startup.

docker compose up -d
docker compose ps

# Health check
curl -s https://meldestelle.ihr-unternehmen.de/health
# {"status":"ok","version":"2.1.0","components":{"database":"ok","redis":"ok"}}

Step 6: Create the first admin

The setup wizard with a one-time token

The wizard asks for a one-time token. Without your own SETUP_TOKEN, it is in the log:

docker compose logs app | grep "Setup token"

Then open https://meldestelle.ihr-unternehmen.de/setup:

  1. Enter the token.
  2. Choose a user name of 3 to 64 characters and a password of at least 12 characters.
  3. Scan the QR code with an authenticator app and enter the six-digit code. This step is mandatory.
  4. Optionally agree that the installation is counted once a day. The default is no.

The first account is a superadmin. Write down the TOTP secret offline: there are no backup codes.

Step 7: Basic configuration in the admin area

Log in at /admin/login. Set up these pages first:

Page What for
/admin/categoriesReport categories, such as finance, workplace safety, discrimination
/admin/locationsLocations; if any are active, the wizard asks for one
/admin/usersmore accounts; new accounts are case handlers, each needs TOTP
/admin/telephone-channelGuide for the oral channel under § 16 Abs. 3 of the Whistleblower Protection Act (HinSchG)
/admin/systeminstalled version and file check

Then send a test report through the form. Check that it appears in the dashboard and that the email arrives.

Deadline reminders

With REMINDER_ENABLED=true, OpenWhistle sends an email before the 7-day or the 3-month deadline under § 17 HinSchG runs out. The check runs every 30 minutes.

Step 8: Make the reporting channel known

Employees need clear information about the internal procedure and about external reporting channels (§ 7 Abs. 3, § 13 Abs. 2 HinSchG). Proven ways:

  • A circular email with the link to the reporting form
  • An entry in the intranet or wiki
  • A note in the code of conduct
  • A notice for employees without a computer workstation, with a phone number

Installing updates

The image is pinned to a version through OPENWHISTLE_VERSION in .env. This is how you update:

# Back up first: a rollback needs the backup
docker compose exec db pg_dump -U openwhistle openwhistle > backup.sql
# The compose file and nginx configuration change with the releases
git pull
# Set OPENWHISTLE_VERSION in .env to the new version, then
docker compose pull
docker compose up -d

Read the entry in the changelog first. Security fixes ship as patch versions; you can subscribe to the releases on GitHub.

What you need to back up

What Why
Database (pg_dump)Reports, messages and, by default, the attachments too
.envholds the keys; without them the backup cannot be read
S3 bucketonly if attachments are stored there (STORAGE_BACKEND=s3)

Look first, then install

The live demo shows the reporting form and the admin area. No account needed, reset every 6 hours.

Open the live demo →

Common problems

"port is already allocated" at startup

Port 80, 443 or 8080 is in use. nginx always publishes the onion access on 127.0.0.1:8080. sudo ss -tlnp shows which process holds the port.

nginx does not start, tls-init reports an error

Usually the key has the wrong permissions or is a symlink. Copy the files, and give privkey.pem to root with 0600 or 0644. The message from tls-init names the path and the reason.

The setup wizard rejects the token

Take the most recent token from the log. Redis keeps it only in memory: if Redis restarts, a new one is created. A SETUP_TOKEN you set yourself always works.

More resources: Full documentation · HinSchG compliance guide · What a free whistleblowing system costs · GitHub Issues