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 composeplugin (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:
- Enter the token.
- Choose a user name of 3 to 64 characters and a password of at least 12 characters.
- Scan the QR code with an authenticator app and enter the six-digit code. This step is mandatory.
- 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/categories | Report categories, such as finance, workplace safety, discrimination |
/admin/locations | Locations; if any are active, the wizard asks for one |
/admin/users | more accounts; new accounts are case handlers, each needs TOTP |
/admin/telephone-channel | Guide for the oral channel under § 16 Abs. 3 of the Whistleblower Protection Act (HinSchG) |
/admin/system | installed 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 |
.env | holds the keys; without them the backup cannot be read |
| S3 bucket | only 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