Why This Stack?
Authentik is an open-source identity provider that supports multiple protocols (OAuth2, SAML, LDAP) and provides excellent flexibility for authentication flows. Combined with:
- Docker Swarm for container orchestration and high availability
- External PostgreSQL for centralized database management
- Azure Communication Services for reliable email delivery
- HAProxy for load balancing and SSL termination
You get a production-ready SSO solution that scales across multiple nodes.
Architecture Overview
The setup consists of:
- 5 Docker Swarm worker nodes running Authentik services
- External PostgreSQL database (outside the swarm) for data persistence
- NFS shared storage for media and template files
- HAProxy for SSL termination and load balancing
- Azure SMTP for email delivery
- .env file for centralized configuration management
This architecture provides clear separation of concerns and makes database maintenance independent of application deployments.
Part 1: Docker Compose Configuration
First, let's set up Authentik with Docker Swarm. Here's the complete structure:
services:
server:
command: server
environment:
# PostgreSQL - External Database
AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_POSTGRESQL__HOST}
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRESQL__NAME}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRESQL__PASSWORD}
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRESQL__USER}
# Secret Key
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
# SMTP Configuration
AUTHENTIK_EMAIL__HOST: ${SMTP_HOST}
AUTHENTIK_EMAIL__PORT: ${SMTP_PORT:-587}
AUTHENTIK_EMAIL__USERNAME: ${SMTP_USERNAME}
AUTHENTIK_EMAIL__PASSWORD: ${SMTP_PASSWORD}
AUTHENTIK_EMAIL__USE_TLS: ${SMTP_USE_TLS:-true}
AUTHENTIK_EMAIL__USE_SSL: ${SMTP_USE_SSL:-false}
AUTHENTIK_EMAIL__TIMEOUT: ${SMTP_TIMEOUT:-30}
AUTHENTIK_EMAIL__FROM: ${SMTP_FROM}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.3}
ports:
- 9002:9000
- 9445:9443
restart: unless-stopped
deploy:
placement:
constraints: [node.role == worker]
volumes:
- nfs-authentik:/media
- nfs-authentik:/templates
worker:
command: worker
environment:
# PostgreSQL - External Database
AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_POSTGRESQL__HOST}
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRESQL__NAME}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRESQL__PASSWORD}
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRESQL__USER}
# Secret Key
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
# SMTP Configuration (same as server)
AUTHENTIK_EMAIL__HOST: ${SMTP_HOST}
AUTHENTIK_EMAIL__PORT: ${SMTP_PORT:-587}
AUTHENTIK_EMAIL__USERNAME: ${SMTP_USERNAME}
AUTHENTIK_EMAIL__PASSWORD: ${SMTP_PASSWORD}
AUTHENTIK_EMAIL__USE_TLS: ${SMTP_USE_TLS:-true}
AUTHENTIK_EMAIL__USE_SSL: ${SMTP_USE_SSL:-false}
AUTHENTIK_EMAIL__TIMEOUT: ${SMTP_TIMEOUT:-30}
AUTHENTIK_EMAIL__FROM: ${SMTP_FROM}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.3}
restart: unless-stopped
user: root
deploy:
placement:
constraints: [node.role == worker]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- nfs-authentik:/media
- nfs-authentik:/certs
- nfs-authentik:/templates
volumes:
nfs-authentik:
driver: local
driver_opts:
device: :/mnt/storage/authentik
o: addr=10.0.0.10,nolock,soft,rw,nfsvers=4
type: nfs
Key Points:
- External Database - PostgreSQL runs on a dedicated server (e.g.,
10.0.0.20:5432), separate from the Docker Swarm - Both server and worker need identical environment variables
- NFS volumes ensure media/template persistence across nodes
- Placement constraints distribute services across worker nodes
- Custom ports (9002, 9445) avoid conflicts with other services
- .env file centralizes all configuration
Why External PostgreSQL?
Running PostgreSQL outside the swarm provides several benefits:
- Independent scaling - Database resources separate from app nodes
- Simplified backups - Standard PostgreSQL backup tools without container complexity
- Better performance - Dedicated hardware for database operations
- Easier maintenance - Database upgrades don't affect app deployments
- Multi-service support - Same database server can support multiple applications

Part 2: Environment File Configuration
Create a .env file in the same directory as your docker-compose.yml:
# .env - Authentik Configuration
# WARNING: Keep this file secure and never commit to version control!
# ============================================================================
# Database Configuration - External PostgreSQL
# ============================================================================
AUTHENTIK_POSTGRESQL__HOST=10.0.0.20
AUTHENTIK_POSTGRESQL__NAME=authentik
AUTHENTIK_POSTGRESQL__USER=authentik_user
AUTHENTIK_POSTGRESQL__PASSWORD=secure_database_password_here
# ============================================================================
# Authentik Secret Key
# Generate with: openssl rand -base64 60
# ============================================================================
AUTHENTIK_SECRET_KEY=your_long_random_secret_key_here
# ============================================================================
# SMTP Configuration - Azure Communication Services
# ============================================================================
SMTP_HOST=smtp.azurecomm.net
SMTP_PORT=587
SMTP_USERNAME=your-azure-communication-endpoint
SMTP_PASSWORD=your-azure-access-key
SMTP_USE_TLS=true
SMTP_USE_SSL=false
SMTP_TIMEOUT=30
SMTP_FROM=noreply@yourdomain.com
# ============================================================================
# Docker Image Configuration
# ============================================================================
AUTHENTIK_IMAGE=ghcr.io/goauthentik/server
AUTHENTIK_TAG=2025.10.3
Alternative: Microsoft 365 SMTP
If using Microsoft 365 instead of Azure Communication Services:
# SMTP Configuration - Microsoft 365
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_USERNAME=service-account@yourdomain.com
SMTP_PASSWORD=app-specific-password-here
SMTP_USE_TLS=true
SMTP_USE_SSL=false
SMTP_TIMEOUT=30
SMTP_FROM=service-account@yourdomain.com
Securing Your .env File
# Set restrictive permissions
chmod 600 .env
chown root:root .env
# Add to .gitignore
echo ".env" >> .gitignore
# Create a template for documentation
cp .env .env.example
# Edit .env.example to remove all sensitive values
Example .env.example for documentation:
# Database Configuration
AUTHENTIK_POSTGRESQL__HOST=your-db-host
AUTHENTIK_POSTGRESQL__NAME=authentik
AUTHENTIK_POSTGRESQL__USER=authentik_user
AUTHENTIK_POSTGRESQL__PASSWORD=your-secure-password
# Secret Key (generate with: openssl rand -base64 60)
AUTHENTIK_SECRET_KEY=your-secret-key
# SMTP Configuration
SMTP_HOST=smtp.azurecomm.net
SMTP_PORT=587
SMTP_USERNAME=your-smtp-username
SMTP_PASSWORD=your-smtp-password
SMTP_FROM=noreply@yourdomain.com
Part 3: External PostgreSQL Setup
Database Server Prerequisites
On your PostgreSQL server (outside the swarm):
-- Connect to PostgreSQL as superuser
psql -U postgres
-- Create database and user
CREATE DATABASE authentik;
CREATE USER authentik_user WITH ENCRYPTED PASSWORD 'secure_password';
-- Grant privileges
GRANT ALL PRIVILEGES ON DATABASE authentik TO authentik_user;
-- Enable required extensions
\c authentik
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Grant schema permissions
GRANT ALL ON SCHEMA public TO authentik_user;
Testing Database Connectivity
From your Docker Swarm manager node:
# Test connection from swarm network
docker run --rm -it postgres:15 psql \
-h 10.0.0.20 \
-U authentik_user \
-d authentik \
-c "SELECT version();"
Part 4: Azure SMTP Configuration
Azure offers two main options for SMTP: Azure Communication Services and Microsoft 365 SMTP Relay.
Option A: Azure Communication Services (Recommended)
This is Azure's modern email service with better deliverability and features.
Setup Steps:
- Create an Azure Communication Services resource in Azure Portal
- Set up an Email Communication Service
- Add and verify your domain (requires DNS records)
- Navigate to your resource and copy the Endpoint and Access Key
Add to your .env file:
# Azure Communication Services
SMTP_HOST=smtp.azurecomm.net
SMTP_PORT=587
SMTP_USERNAME=<your-azure-endpoint>
SMTP_PASSWORD=<your-access-key>
SMTP_USE_TLS=true
SMTP_USE_SSL=false
SMTP_TIMEOUT=30
SMTP_FROM=noreply@yourdomain.com
Option B: Microsoft 365 SMTP Relay
For organizations already using Microsoft 365:
Add to your .env file:
# Microsoft 365
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_USERNAME=service-account@yourdomain.com
SMTP_PASSWORD=<password-or-app-specific-password>
SMTP_USE_TLS=true
SMTP_USE_SSL=false
SMTP_TIMEOUT=30
SMTP_FROM=service-account@yourdomain.com
Important: If using MFA on your Microsoft 365 account, generate an app-specific password.
Testing Email Configuration
After deploying, test the email functionality:
# Deploy the stack
docker stack deploy -c docker-compose.yml authentik
# Watch logs
docker service logs -f authentik_server
# Send test email from Authentik UI
# Navigate to: System → Settings → Test Email
Common issues:
- Authentication failures: Verify credentials in .env file
- TLS errors: Ensure
USE_TLS=trueandUSE_SSL=false - Rejected emails: Check domain verification in Azure
- Connection timeouts: Verify firewall rules allow outbound port 587

Part 5: HAProxy Integration
Now let's integrate Authentik with HAProxy for high availability and SSL termination.
Frontend Configuration
Add the ACL and routing rules to your HTTPS frontend:
frontend https_frontend
# SSL binding with HTTP/2 support
bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
# Security headers
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-XSS-Protection "1; mode=block"
http-response set-header Referrer-Policy strict-origin-when-cross-origin
http-response set-header Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Remove server identification
http-response del-header Server
http-response del-header X-Powered-By
# Standard reverse-proxy headers
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header Forwarded "proto=https;host=%[req.hdr(Host)];for=%[src]"
# Authentik routing
acl host_authentik hdr(host) -i sso.example.com
use_backend authentik_backend if host_authentik
Backend Configuration
backend authentik_backend
description Authentik SSO Identity Provider
balance roundrobin
# Extended timeouts for authentication flows
timeout server 60s
timeout tunnel 1h
# WebSocket support for real-time updates
option http-server-close
option forwardfor
# Health check
option httpchk GET /
http-check send meth GET uri / hdr Host sso.example.com
http-check expect rstatus (200|302|404)
option log-health-checks
# High availability across three nodes
server docker01 10.0.1.10:9445 check ssl verify none inter 20s rise 2 fall 3 maxconn 50
server docker02 10.0.1.11:9445 check ssl verify none inter 20s rise 2 fall 3 maxconn 50
server docker03 10.0.1.12:9445 check ssl verify none inter 20s rise 2 fall 3 maxconn 50
SSL Certificate Setup
Create a combined PEM file for HAProxy:
# Using Let's Encrypt certificates
cat /etc/letsencrypt/live/sso.example.com/fullchain.pem \
/etc/letsencrypt/live/sso.example.com/privkey.pem \
> /etc/haproxy/certs/sso.example.com.pem
chmod 600 /etc/haproxy/certs/sso.example.com.pem
chown haproxy:haproxy /etc/haproxy/certs/sso.example.com.pem
Testing and Validation
# Test HAProxy configuration
haproxy -c -f /etc/haproxy/haproxy.cfg
# Reload HAProxy (zero downtime)
systemctl reload haproxy
# Monitor logs
tail -f /var/log/haproxy.log
# Test backend connectivity
curl -k https://10.0.1.10:9445
curl -k https://10.0.1.11:9445
curl -k https://10.0.1.12:9445
Part 6: High Availability Considerations
Health Checks
HAProxy performs several types of checks:
- HTTP checks (
option httpchk) - Verifies application responds correctly - Rise/fall thresholds - Prevents flapping (2 successes to mark up, 3 failures to mark down)
- Inter intervals - Check every 20 seconds
Session Persistence
For Authentik, you typically don't need sticky sessions because:
- Authentication state is stored in the external PostgreSQL database
- JWT tokens are stateless
- Sessions are shared across all nodes via the centralized database
However, for improved performance during active authentication flows:
backend authentik_backend
# Optional: sticky sessions based on source IP
stick-table type ip size 100k expire 30m
stick on src
# ... rest of configuration
Load Balancing Strategy
Roundrobin is ideal for Authentik because:
- Distributes load evenly across all nodes
- No single point of failure
- Simple and predictable behavior
- Works well with stateless applications backed by external database
External Database Benefits
With an external PostgreSQL database:
- No split-brain scenarios - Single source of truth for all nodes
- Simplified scaling - Add/remove app nodes without database concerns
- Independent database tuning - Optimize PostgreSQL separately from application
- Easier monitoring - Standard database monitoring tools work without container complexity

Next Steps
- Configure OIDC/SAML - Connect your applications
- Set up MFA - Add WebAuthn or TOTP
- Customize flows - Tailor authentication experience
- Enable LDAP - Integrate with legacy apps
- Implement monitoring - Add Prometheus/Grafana
- Set up automated backups - Database and configuration backups
- Configure log aggregation - Centralized logging with ELK or similar