The Password Sprawl Problem in Self-Hosted Labs
Homelabs grow fast. What begins as a single Plex server and a Pi-hole container quickly expands into 15 or 20 distinct services. Before long, you are juggling separate logins across Portainer, Proxmox, TrueNAS, Grafana, and Nextcloud.
Security quickly becomes inconsistent. Some tools support TOTP or hardware security keys out of the box. Others rely on plain HTTP basic auth. A few older utilities offer no authentication at all, leaving them wide open if exposed beyond your local subnet. Updating credentials turns into a tedious chore. Worse, revoking access for a compromised account means logging into every single dashboard manually.
A centralized Identity Provider (IdP) fixes this mess. Instead of letting each application manage credentials independently, you funnel every login request through a single, hardened gateway.
Core Concepts: How Authentik Secures Your Stack
Authentik is an open-source, self-hosted identity platform designed for high flexibility. It operates as the single source of truth for your network identity.
1. Identity Provider (IdP) vs. Service Provider (SP)
In this architecture, Authentik acts as the Identity Provider (IdP). It stores your user accounts, verifies passwords, and validates 2FA tokens. Your individual apps (like Grafana, Vaultwarden, or Paperless-ngx) act as Service Providers (SP). When you access an app, it delegates the entire handshake to Authentik instead of handling passwords directly.
2. Supported Authentication Protocols
- OpenID Connect (OIDC) / OAuth2: The gold standard for modern web applications. The client app redirects you to Authentik, receives a signed JSON Web Token (JWT), and extracts your username, email, and group memberships.
- SAML 2.0: An XML-based protocol widely used by enterprise software, network switches, and hypervisors like VMware ESXi or Proxmox VE.
- Forward Auth (Proxy Provider): Built for legacy apps with zero native auth (such as qBittorrent or Radarr). Authentik hooks into your reverse proxy (Traefik, Nginx Proxy Manager, or Caddy) and intercepts incoming HTTP traffic at the network edge, blocking unauthorized requests before they ever reach the container.
3. Resource Footprint and Architecture
A standard Authentik stack requires roughly 1.5 GB to 2.5 GB of RAM at idle and runs across four dedicated containers:
- PostgreSQL 16: Stores user records, role bindings, flow stages, and audit events.
- Redis (Alpine): Caches active sessions, manages rate-limiting thresholds, and coordinates the Celery background queue.
- Authentik Server: Powers the admin UI, user self-service portal, and core API endpoints (listens on ports
9000and9443). - Authentik Worker: Handles asynchronous tasks, email delivery, LDAP directory synchronization, and scheduled cleanups.
Hands-On Practice: Deploying Authentik on Docker
Step 1: Set Up Directory Structure and Generate Secrets
Create a home directory for Authentik and generate 50+ character cryptographic strings for session signing and database credentials.
mkdir -p ~/homelab/authentik
cd ~/homelab/authentik
mkdir -p ./media ./custom-templates ./certs
Generate secure random strings using openssl and populate your .env file:
# Generate strong keys
AUTHENTIK_SECRET_KEY=$(openssl rand -base64 36)
PG_PASSWORD=$(openssl rand -base64 24)
# Populate environment variables
cat <<EOF > .env
PG_PASS=${PG_PASSWORD}
AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED=false
AUTHENTIK_DISABLE_UPDATE_CHECK=false
AUTHENTIK_DISABLE_STARTUP_ANALYTICS=false
EOF
Step 2: Write the Docker Compose Configuration
Create a docker-compose.yml file in your directory with explicit healthchecks to guarantee proper service startup ordering:
services:
postgresql:
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
timeout: 5s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS}
POSTGRES_USER: authentik
POSTGRES_DB: authentik
env_file:
- .env
redis:
image: docker.io/library/redis:alpine
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
timeout: 3s
retries: 5
volumes:
- redis_data:/data
server:
image: ghcr.io/goauthentik/server:2024.8.3
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./media:/media
- ./custom-templates:/templates
env_file:
- .env
ports:
- "9000:9000"
- "9443:9443"
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
worker:
image: ghcr.io/goauthentik/server:2024.8.3
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./media:/media
- ./custom-templates:/templates
- ./certs:/certs
- /var/run/docker.sock:/var/run/docker.sock
env_file:
- .env
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
volumes:
postgres_data:
redis_data:
Start the stack:
docker compose up -d
Tail the logs during initial database migrations (typically takes 30–60 seconds on modern SSDs):
docker compose logs -f server
Step 3: Initial Administrator Provisioning
Open your browser and navigate to http://<SERVER_IP>:9000/if/flow/initial-setup/.
- Set a strong master password for the built-in
akadminaccount. - Log in to the management console at
http://<SERVER_IP>:9000. - Go to Directory > Users and create your everyday admin account. Add this account to the
authentik Adminsgroup, then sign out ofakadmin.
Step 4: Configure an OIDC Application (Example: Grafana)
Connecting an application via OIDC requires two linked components: a Provider (the protocol endpoint) and an Application (the user-facing catalog entry).
- Go to Applications > Providers and click Create.
- Select OAuth2/OpenID Provider and configure the following parameters:
- Name:
Grafana-OIDC - Authentication Flow:
default-authentication-flow - Authorization Flow:
default-provider-authorization-explicit-consent - Client Type:
Confidential - Redirect URI:
https://grafana.internal.home/login/generic_oauth
- Name:
- Save the provider and record the generated Client ID and Client Secret.
- Go to Applications > Applications, click Create, set the name to
Grafana, the slug tografana, and attach theGrafana-OIDCprovider created in the previous step.
Now match these values in your Grafana configuration (grafana.ini or container environment variables):
[auth.generic_oauth]
enabled = true
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
scopes = openid profile email
auth_url = https://auth.internal.home/application/o/authorize/
token_url = https://auth.internal.home/application/o/token/
api_url = https://auth.internal.home/application/o/userinfo/
Step 5: Enforce WebAuthn and TOTP Multi-Factor Authentication
Lock down all downstream applications at once by adding a mandatory 2FA stage to your core login flow:
- Navigate to Flows and Stages > Flows and open
default-authentication-flow. - Select Stage Bindings and edit the
default-authentication-mfa-validationentry. - Change the device validation setting from Optional to Enforce MFA.
- Authentik will now force users to register a passkey, YubiKey (FIDO2), or TOTP authenticator before granting access to any downstream dashboard.
Expanding to the Rest of Your Stack
Once your IdP is live, onboarding new services takes only a few minutes. Applications with native OAuth2 support plug directly into OIDC providers. For everything else, deploying an Authentik Outpost forward proxy alongside Nginx Proxy Manager or Traefik puts all legacy web interfaces behind your centralized 2FA perimeter.
You gain a single dashboard to audit active sessions, push group-based permissions, and instantly revoke credentials across your entire homelab.

