Install with Docker Compose

Docker Compose starts everything with one command: application, PostgreSQL, Redis and nginx.

Step 1 — Clone the repository

bash
$ git clone https://github.com/openwhistle/OpenWhistle.git
$ cd OpenWhistle

Step 2 — Configure environment

At minimum set SECRET_KEY, DATABASE_URL and REDIS_URL; every other setting is in the configuration table.

bash
$ cp .env.example .env
$ nano .env

# The production stack (TLS, nginx); every docker compose command below uses it:
COMPOSE_FILE=docker-compose.prod.yml

# Minimum required values. The production Redis requires REDIS_PASSWORD,
# so it goes into REDIS_URL too, as POSTGRES_PASSWORD goes into DATABASE_URL:
# openssl rand -hex 32 — a new value for each key:
SECRET_KEY=<64 hex characters>
ENCRYPTION_KEY=<64 hex characters>
POSTGRES_PASSWORD=db-password
REDIS_PASSWORD=redis-password
DATABASE_URL=postgresql+asyncpg://openwhistle:db-password@db:5432/openwhistle
REDIS_URL=redis://:redis-password@redis:6379/0

Step 3 — Start services

bash
$ docker compose up -d
► Network openwhistle_default      Created
► Container openwhistle-db-1        Started
► Container openwhistle-redis-1     Started
► Container openwhistle-app-1       Started
► Container openwhistle-nginx-1     Started
TLS is on by default

nginx serves HTTPS on 443 and redirects plain HTTP on 80. Put your fullchain.pem and privkey.pem (e.g. from certbot) into nginx/certs/ and restart.

  • Copy them, do not symlink: a link into /etc/letsencrypt/live dangles inside the container.
  • Keep the key root-owned 0600, or 0644. Otherwise tls-init stops and names the path and the reason.
  • Without them, the one-shot tls-init service generates a self-signed certificate before nginx starts. It is for TLS_HOSTNAME (default localhost), a .env variable only docker-compose.prod.yml reads. After a change, docker compose up -d tls-init nginx issues and serves a new one.
  • To renew, drop the new certificate into nginx/certs/ and run docker compose up -d tls-init nginx. Restarting nginx alone keeps the old one: nginx never re-reads nginx/certs/ itself.
Self-signed is for first boot only

It lets the stack come up before you have a real certificate, not to serve users. A browser that clicks through its warning gets an HSTS pin (Strict-Transport-Security: max-age=31536000; includeSubDomains; preload) for that hostname. Install a real certificate before anyone but you visits.

Step 4 — Run the setup wizard

Unless you set SETUP_TOKEN yourself, read the one-time setup token from the log:

bash
$ docker compose logs app | grep "Setup token"

Then open https://<TLS_HOSTNAME>/setup (https://localhost/setup by default) and follow the First-Run Wizard. The account it creates is a superadmin: it manages organisations and grants every role.

Database migrations

Alembic migrations run automatically at startup. The app serves no traffic until every one has been applied.

First-Run Wizard

On first boot, /setup serves the wizard: one page, four parts. Once an admin account exists it redirects to /admin/login for good.

Wizard steps

  1. Setup token
    From SETUP_TOKEN or the log (Installation, step 4).
  2. Admin account
    A username of 3–64 characters (letters, digits, spaces and . _ @ -). A password of at least 12 characters and at most 72 bytes.
  3. TOTP
    Scan the QR code with an authenticator app (Google Authenticator, Authy, Bitwarden, etc.) and enter its 6-digit code. Mandatory; it cannot be skipped.
  4. Installation count
    Optional and unchecked: may this installation be counted once a day (what is sent)? Changeable under Admin → System.
Save your TOTP secret

Write down the TOTP secret shown during setup and keep it offline. There are no backup codes. A lost authenticator is reset by a superadmin or on the host (Lost password or authenticator).

Edit this page on GitHub