Installation Guide

How to stand up a working Headless Auth Platform instance. Two paths are covered: a local developer setup (fastest) and a Docker Compose stack (closest to self-hosted production). The application lives in the target/ directory of the repository.

Commands are run from inside target/ unless stated otherwise. For a gentler first-run walkthrough see Getting started.

Prerequisites

  • Docker with Docker Compose (for PostgreSQL 16 + Redis 7).
  • uv — the Python package manager (install) — for the local developer path.
  • A terminal and a clone of the repository.

Configuration

All configuration is via environment variables with the HAP_ prefix. Copy the template and fill in the two values that have no safe default:

cd target
cp .env.example .env
VariableRequiredPurpose
HAP_DATABASE_URLyesPostgreSQL connection (async driver).
HAP_REDIS_URLyesRedis connection.
HAP_MASTER_ENCRYPTION_KEYyesBase64 32-byte key for at-rest encryption. Generate below.
HAP_ADMIN_API_KEYyes (for admin APIs)Bootstrap key for tenant/app provisioning.
HAP_ISSUER_BASE_URLrecommendedPublic base URL; per-tenant issuer is …/t/{slug}.
HAP_KEY_PROVIDERoptionalenv (default; encrypted PEMs on local disk) or db (encrypted PEMs in Postgres). See "Signing keys" below.
HAP_DEFAULT_JWKS_ALGORITHMoptionalRS256 (default), ES256, or EdDSA.
HAP_EMAIL_MODEoptionalconsole (default; logs the code) or smtp (send real email). See "Email / OTP delivery" below.
HAP_EMAIL_FROMfor smtpSender address; must be a verified identity on your mail provider.
HAP_SMTP_HOST / HAP_SMTP_PORTfor smtpSMTP server (e.g. email-smtp.<region>.amazonaws.com / 587).
HAP_SMTP_USERNAME / HAP_SMTP_PASSWORDfor smtpSMTP credentials (secret).
HAP_SMTP_STARTTLS / HAP_SMTP_SSLoptionaltrue/false; default STARTTLS on 587.

Signing keys (HAP_KEY_PROVIDER)

Each tenant gets its own signing key, stored encrypted (AES-GCM with HAP_MASTER_ENCRYPTION_KEY) — never in plaintext, never committed.

  • env (default) — keeps the encrypted key on the local filesystem (HAP_KEYSTORE_DIR). Fine for a developer machine with a persistent disk.
  • db — keeps the encrypted key in Postgres. Use this for any deployment on ephemeral/ stateless containers (e.g. Kubernetes): a filesystem keystore is wiped on pod restart, which loses tenants' signing keys. The master key stays separate (a secret, never in the DB), so a database compromise alone yields no usable key material.

Email / OTP delivery (HAP_EMAIL_MODE)

Passwordless login and magic links email a one-time code.

  • console (default) — the code is logged to stdout, not emailed. Dev only; real users receive nothing.
  • smtp — sends real email via any SMTP relay, including Amazon SES (set HAP_SMTP_HOST to the SES SMTP endpoint and use SES SMTP credentials). The HAP_EMAIL_FROM identity must be verified with the provider, and (for SES) the account must be out of the sandbox to email arbitrary recipients. Delivery is best-effort and never blocks a login response.

Generate a master key:

python -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())"

Put the result in HAP_MASTER_ENCRYPTION_KEY, and set HAP_ADMIN_API_KEY to a strong random string.


Path A — Local developer setup

cd target
uv sync --extra dev                       # install dependencies
docker compose up -d postgres redis       # start the datastores
uv run alembic upgrade head               # create the database schema
uv run uvicorn hap.main:app --reload      # start the service on :8000

Verify it's healthy (second terminal):

curl -s localhost:8000/healthz            # {"status":"ok"}
curl -s localhost:8000/readyz             # database + redis both "ok"

If /readyz reports ok for both database and redis, the instance is running.


Path B — Full Docker Compose stack (self-host style)

This builds the application image and runs it alongside PostgreSQL and Redis. The app container applies database migrations on startup.

cd target
# ensure .env has HAP_MASTER_ENCRYPTION_KEY and HAP_ADMIN_API_KEY set
docker compose up --build                 # app + postgres + redis (+ worker profiles)

Then, from your host:

curl -s localhost:8000/readyz             # expect status "ok"

The Compose file also defines Celery worker/beat services (under a workers profile) for background jobs.

Path C — Published image with the plain Docker CLI

Prefer to pull a prebuilt image and wire up the containers yourself (no Compose, no source checkout)? Follow Local login guidelines, a step-by-step Docker-CLI quickstart for tooltwist/wbsp-hap that stands up Postgres + Redis + HAP and configures a tenant and application so locally-run apps can authenticate. The app image applies migrations on startup, same as Path B.

First-run: create a tenant

The platform ships with no tenants. Create one (and confirm discovery resolves):

curl -s -X POST localhost:8000/v1/tenants \
  -H "X-Admin-Key: <HAP_ADMIN_API_KEY>" -H "Content-Type: application/json" \
  -d '{"name":"Demo","slug":"demo","password_policy":{"min_length":8}}'

curl -s localhost:8000/t/demo/.well-known/openid-configuration   # JSON config = success

You now have a working instance. To connect an application, continue with the HAP integration guide.

Operating notes

  • Database/cache: PostgreSQL 16 and Redis 7 are required; SQLite is not supported.
  • Migrations: schema changes are applied with uv run alembic upgrade head and can be reversed with uv run alembic downgrade base.
  • Signing keys: keys are generated per tenant and stored encrypted. The env provider keeps them on local disk (fine for a dev box); for any stateless/containerised deployment use HAP_KEY_PROVIDER=db so keys survive restarts (see "Signing keys" under Configuration).
  • Health checks: wire /healthz (liveness) and /readyz (readiness) into your orchestrator.
  • Managed option: prefer not to operate it yourself? A managed offering is available — contact us.

Troubleshooting

  • /readyz shows database: error → confirm HAP_DATABASE_URL and that the Postgres container is healthy (docker compose ps).
  • Startup error about the master key → ensure HAP_MASTER_ENCRYPTION_KEY is set to a base64-encoded 32-byte value.
  • Admin calls return 401 → check the X-Admin-Key header matches HAP_ADMIN_API_KEY.

See also