infra/authentik — Self-Hosted Authentik IdP Stack
This directory contains the artifacts to deploy a self-hosted Authentik
identity-provider instance on the staging Hetzner host (boilerplate-staging,
see docs/improvement-plan/archive/STAGING-ACCESS.md) via the existing
Coolify-managed Docker-Compose runtime.
Purpose
- Centralise admin authentication behind an external IdP (Phase 09 #82) instead of trusting the application's own user table
- Enforce MFA-TOTP for super-admins at the IdP layer (group-policy)
- Provide a group-based access model (
toolctrl-admin,toolctrl-staff,toolctrl-users) that the admin panel + future tenant-SSO consume - Provide a federation target for the Backlog #66 customer-tenant-SSO (second OIDC provider definition is shipped as a disabled placeholder)
- Aggregate auth-audit events (login / logout / mfa-enroll / group-change) so they can be ingested by the Phase 10 audit-log (#67)
The companion ultra-lite alternative (GLAuth, LDAP-only, ~50 MB RAM) is
documented in docs/operations/authentik-alternatives.md and is not
the default — it lacks OIDC, MFA-enforcement, brand-flows, and blueprints.
Prerequisites
- Hetzner host with ≥ 2 GB RAM free for the Authentik stack (server: ~600 MB, worker: ~400 MB, postgres: ~300 MB, redis: ~150 MB, plus headroom). The staging host has 30 GiB RAM with plenty of headroom.
- Docker Engine ≥ 24 + Docker Compose v2 (provided by Coolify)
- Traefik v3.6 reverse-proxy from
coolify-proxy(Let's Encrypt ACME via theletsencryptcert-resolver) - DNS record
auth.boiler.saas-ctrl.com→46.225.5.93(Cloudflare-managed; seedocs/operations/authentik-setup.mdfor thecf-clisnippet that creates it) - Sentry self-hosted stack from Phase 02 #29 reachable at
sentry.boiler.saas-ctrl.com(Authentik containers report their own errors to it viaAUTHENTIK_SENTRY__DSN)
Layout
infra/authentik/
├── README.md # this file
├── .gitignore # excludes runtime volumes + secrets
├── UPSTREAM.md # pinned Authentik tag + image pin rationale
├── docker-compose.yml # 4-service compose: server, worker, postgresql, redis
├── traefik.labels.yml # canonical Traefik label-set for auth.<domain>
├── .env.example # template for AUTHENTIK_* secrets
├── bootstrap/
│ ├── bootstrap.sh # waits for /-/health/ready/ then loads blueprints
│ └── blueprints/
│ ├── 01-groups.yaml # toolctrl-admin / -staff / -users groups + attrs
│ ├── 02-policies.yaml # password policy + MFA-enforce + brute-force
│ └── 03-application-toolctrl-admin.yaml # OIDC provider for admin panel
├── backup/ # daily backup container
│ ├── Dockerfile
│ ├── backup.sh
│ └── crontab
└── scripts/
└── smoke.sh # post-deploy smoke checks (curl-only)
Deploy
See docs/operations/authentik-setup.md for the full Coolify-deploy + DNS +
backup + restore runbook. Deployment is operator-owned — this directory
ships the artifacts; the operator runs the Coolify side.
High-level:
- Operator creates the Coolify "Docker Compose" service against this repo
pointing at
infra/authentik/docker-compose.yml. - Operator copies
.env.example→ Coolify-Vault and fills in secrets (AUTHENTIK_SECRET_KEYfromopenssl rand -base64 60, etc.). - Coolify deploy boots postgres, redis, then
server+worker. - Operator runs
bootstrap/bootstrap.shonce against the running stack to apply blueprints (groups + policies + admin-panel OIDC provider). - Operator runs
scripts/smoke.shto verify TLS, health, OIDC discovery.
Versioning
- Authentik pin:
ghcr.io/goauthentik/server:2026.2.3andghcr.io/goauthentik/proxy:2026.2.3(seeUPSTREAM.mdfor rationale). - Postgres pin:
postgres:16-alpine. - Redis pin:
redis:7-alpine.
Scope
- Authentik server + worker + dedicated postgresql + dedicated redis — these databases MUST NOT be shared with the boilerplate application's own Postgres/Redis (separate compromise blast-radius + separate backup/restore cycle).
- No Outposts — pure OIDC use-case; the reverse-proxy outpost concept is intentionally out of scope for this phase.
- No public self-registration — admin-only invitation flow.