Complete Lab Guide — Authentik + Traefik + Docker
Welcome to the Zero-Trust SSO Gateway lab. In this comprehensive hands-on exercise, you will build an enterprise-grade identity infrastructure using open-source technologies. By the end of this lab, you will have a fully functional Single Sign-On (SSO) system that protects your self-hosted applications with modern Zero Trust security principles.
The skills you develop here directly translate to enterprise IAM platforms like Okta, Microsoft Entra ID (Azure AD), Ping Identity, and ForgeRock. Understanding how to deploy and configure an Identity Provider (IdP) is a core competency for IAM Engineers earning $80K-$150K annually.
Upon successful completion of this lab, you will be able to:
| Technology | Purpose | Enterprise Equivalent |
|---|---|---|
| Authentik | Identity Provider (IdP) — authentication, authorization, user management | Okta, Azure AD, Ping Identity |
| Traefik | Reverse Proxy — traffic routing, TLS termination, forward auth | F5, NGINX Plus, AWS ALB |
| Docker & Docker Compose | Container orchestration — service deployment and management | Kubernetes, ECS, OpenShift |
| PostgreSQL | Database — stores identity data, policies, audit logs | RDS, Azure SQL, Oracle |
| Redis | Cache — session management, rate limiting, task queues | ElastiCache, Azure Cache |
| Let's Encrypt | Certificate Authority — free, automated TLS certificates | DigiCert, Venafi, AWS ACM |
Before starting this lab, ensure you have the following resources and access rights prepared. Taking time to verify prerequisites will prevent interruptions during the hands-on phases.
This lab requires two separate systems. Do not install Docker on your local workstation unless it IS your server.
| Requirement | HOST Machine | SERVER Machine |
|---|---|---|
| Operating System | Windows 10/11, macOS, or Linux | Ubuntu 22.04/24.04 LTS or Debian 12 |
| RAM | 4GB+ (for browser and terminal) | 4GB minimum, 8GB recommended |
| Storage | — | 20GB+ free disk space |
| Network | Internet access | Static IP or DHCP reservation, ports 80/443 accessible |
| Software to Install | SSH client, web browser | Docker, Docker Compose (we'll install these) |
yourdomain.com) — You can use Cloudflare, Namecheap, Google Domains, etc.authentik.yourdomain.com, traefik.yourdomain.comFill in these values before proceeding. You'll reference them throughout the lab.
| Item | Your Value | Example |
|---|---|---|
| Your Domain | ________________ |
homelab.example.com |
| Server Public IP | ________________ |
203.0.113.50 |
| Server Local IP | ________________ |
192.168.1.100 |
| SSH Username | ________________ |
admin |
| Email for SSL Certs | ________________ |
admin@example.com |
Configure your HOST workstation with the tools needed to connect to and manage your server.
In this phase, you will install and configure an SSH client on your local workstation. SSH (Secure Shell) provides encrypted communication between your HOST machine and the SERVER where all services will run.
SSH is the industry-standard protocol for secure remote server administration. In enterprise environments, you'll use the same concepts with jump hosts, bastion servers, and privileged access management (PAM) solutions like CyberArk.
Windows 10 (version 1809+) and Windows 11 include OpenSSH client by default. Let's verify it's installed:
Right-click the Start button Select "Windows Terminal (Admin)" or "PowerShell (Admin)"
# Check if OpenSSH Client capability is installed
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Client*'
If the previous command shows "NotPresent", run this command to install:
# Install OpenSSH Client (requires admin privileges)
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
Confirm SSH is working by checking the version:
ssh -V
macOS includes OpenSSH by default. No installation required.
Press Cmd + Space, type "Terminal", and press Enter.
Verify SSH is available:
ssh -V
Most Linux distributions include OpenSSH client. If not, install it:
# Update package lists
sudo apt update
# Install OpenSSH client
sudo apt install openssh-client -y
# Verify installation
ssh -V
Replace username with your SSH user and server-ip with your server's IP address:
# Connect to your server (replace with your values)
ssh username@server-ip
# Example:
# ssh admin@192.168.1.100
# ssh admin@203.0.113.50
When connecting for the first time, you'll see a message like:
Type yes and press Enter. Then enter your password when prompted.
You should now see your server's command prompt. Confirm by checking the hostname:
# Display current hostname
hostname
# Display current user and hostname
whoami && hostname
You have successfully configured your HOST machine and established a secure SSH connection to your SERVER. All remaining commands in this lab will be executed on the SERVER via this SSH session.
Update the operating system, configure the firewall, and install Docker + Docker Compose.
From this point forward, ALL commands are executed on the SERVER via your SSH session. The command prompt should show your server's hostname, not your local machine.
Start by updating all system packages to ensure you have the latest security patches and software versions.
# Update the package index (list of available packages)
sudo apt update
# Upgrade all installed packages to their latest versions
# -y flag automatically confirms prompts
sudo apt upgrade -y
# Install common utilities we'll need
sudo apt install -y curl wget git nano htop net-tools
apt update — Refreshes the list of available packages from repositoriesapt upgrade — Installs newer versions of packages currently installed-y flag — Automatically answers "yes" to confirmation promptssudo — Runs the command with superuser (root) privilegesUFW (Uncomplicated Firewall) is a user-friendly interface for managing iptables firewall rules. We'll configure it to allow only necessary traffic.
# Install UFW (may already be installed on Ubuntu)
sudo apt install ufw -y
# IMPORTANT: Allow SSH first to prevent lockout!
sudo ufw allow ssh
# Allow HTTP (port 80) - required for Let's Encrypt certificate verification
sudo ufw allow 80/tcp
# Allow HTTPS (port 443) - all secure web traffic
sudo ufw allow 443/tcp
# Enable the firewall
sudo ufw enable
When prompted "Command may disrupt existing ssh connections. Proceed with operation (y|n)?", type y and press Enter.
ALWAYS allow SSH before enabling the firewall! If you skip this step, you will be locked out of your server and unable to reconnect remotely.
Check the firewall status and rules:
sudo ufw status verbose
Docker is a platform for developing, shipping, and running applications in containers. Containers package an application with all its dependencies, ensuring consistent behavior across environments.
# Remove any old Docker packages that might conflict
# It's OK if these packages aren't found
sudo apt remove docker docker-engine docker.io containerd runc 2>/dev/null || true
# Install packages needed to use HTTPS repositories
sudo apt install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
# Create directory for Docker's GPG key
sudo install -m 0755 -d /etc/apt/keyrings
# Download and add Docker's official GPG key
# This verifies that packages are authentically from Docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# Set proper permissions on the key file
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add Docker repository to apt sources
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Update apt to recognize the new Docker repository
sudo apt update
# Install Docker Engine, CLI, containerd, and plugins
sudo apt install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
By default, Docker commands require sudo. Adding your user to the docker group allows running Docker without sudo.
# Add current user to the docker group
sudo usermod -aG docker $USER
# Apply the group change without logging out
# This creates a new shell with updated group membership
newgrp docker
Verify Docker is installed correctly and your user can run Docker commands:
# Check Docker version
docker --version
# Check Docker Compose version
docker compose version
# Run a test container (downloads small image, runs, and exits)
docker run hello-world
Your server is now updated, firewalled, and has Docker installed. You're ready to deploy the core services.
Configure DNS, create Docker Compose stack, and deploy Traefik + Authentik.
Before deploying services, you need to point your domain to your server. Log in to your DNS provider (Cloudflare, Route53, Namecheap, etc.) and create these records:
| Type | Name | Value | TTL | Purpose |
|---|---|---|---|---|
A |
authentik |
Your Server Public IP | Auto / 300 | Authentik web interface |
A |
traefik |
Your Server Public IP | Auto / 300 | Traefik dashboard |
A |
* (wildcard) — optional |
Your Server Public IP | Auto / 300 | Future apps (*.yourdomain.com) |
If using Cloudflare, set the proxy status to "DNS only" (gray cloud) initially. You can enable the proxy later after SSL is working. Proxied traffic (orange cloud) can interfere with Let's Encrypt certificate issuance.
Wait 2-5 minutes for DNS propagation, then verify the records:
# Check DNS resolution (replace with your domain)
nslookup authentik.yourdomain.com
# Alternative using dig
dig authentik.yourdomain.com +short
# Create main directory for all identity services
mkdir -p ~/identity-stack
# Create subdirectories for configuration and data
mkdir -p ~/identity-stack/traefik
mkdir -p ~/identity-stack/authentik/media
mkdir -p ~/identity-stack/authentik/templates
mkdir -p ~/identity-stack/postgres-data
mkdir -p ~/identity-stack/redis-data
# Navigate to the project directory
cd ~/identity-stack
# Verify the structure
tree . 2>/dev/null || ls -la
Authentik requires a secret key for encryption and a secure database password. We'll generate these using OpenSSL.
# Ensure we're in the project directory
cd ~/identity-stack
# Generate a random secret key for Authentik (50 characters)
# This is used to encrypt session data and tokens
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 36)" >> .env
# Generate a random password for PostgreSQL database
echo "PG_PASS=$(openssl rand -base64 24)" >> .env
# View the generated secrets (keep these safe!)
cat .env
The .env file contains sensitive credentials. In enterprise environments, these secrets would be stored in a secrets manager like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault.
.env files to version control (Git)chmod 600 .envThis file defines Traefik's core settings, entrypoints, and certificate resolver.
# Create Traefik configuration file
# IMPORTANT: Replace YOUR_EMAIL@example.com with your real email
cat > ~/identity-stack/traefik/traefik.yml << 'EOF'
# ============================================
# TRAEFIK STATIC CONFIGURATION
# ============================================
# Global settings
global:
checkNewVersion: true
sendAnonymousUsage: false
# API and Dashboard configuration
api:
dashboard: true # Enable Traefik dashboard
insecure: false # Dashboard requires authentication
# Logging configuration
log:
level: INFO # DEBUG, INFO, WARN, ERROR
filePath: "/var/log/traefik/traefik.log"
accessLog:
filePath: "/var/log/traefik/access.log"
bufferingSize: 100
# Entrypoints define how traffic enters Traefik
entryPoints:
# HTTP entrypoint - redirects all traffic to HTTPS
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
# HTTPS entrypoint - main secure traffic handler
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
# Certificate resolvers for automatic TLS
certificatesResolvers:
letsencrypt:
acme:
# IMPORTANT: Replace with YOUR email address
email: YOUR_EMAIL@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
# Provider configuration
providers:
# Docker provider - auto-discovers containers
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false # Only expose containers with traefik.enable=true
network: identity-network
# File provider - for additional dynamic config
file:
directory: /etc/traefik/dynamic
watch: true
EOF
Replace the placeholder email with your real email address. Let's Encrypt uses this for certificate expiration warnings.
# Replace placeholder with your actual email
# Example: sed -i 's/YOUR_EMAIL@example.com/admin@mycompany.com/g' ...
sed -i 's/YOUR_EMAIL@example.com/YOUR_REAL_EMAIL@domain.com/g' \
~/identity-stack/traefik/traefik.yml
# Verify the change
grep "email:" ~/identity-stack/traefik/traefik.yml
Let's Encrypt certificates will be stored in this file. It must exist with proper permissions before Traefik starts.
# Create the letsencrypt directory
mkdir -p ~/identity-stack/letsencrypt
# Create empty acme.json file
touch ~/identity-stack/letsencrypt/acme.json
# Set restrictive permissions (required by Traefik)
# 600 = owner can read/write, no one else can access
chmod 600 ~/identity-stack/letsencrypt/acme.json
This is the main deployment file that defines all services and their configurations.
# Create docker-compose.yml
# IMPORTANT: Replace 'yourdomain.com' with your actual domain
cat > ~/identity-stack/docker-compose.yml << 'EOF'
# ============================================
# IDENTITY STACK - DOCKER COMPOSE
# Authentik + Traefik + PostgreSQL + Redis
# ============================================
version: "3.8"
services:
# ----------------------------------------
# TRAEFIK - Reverse Proxy & Edge Router
# ----------------------------------------
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80" # HTTP (redirects to HTTPS)
- "443:443" # HTTPS
volumes:
# Docker socket - allows Traefik to detect containers
- /var/run/docker.sock:/var/run/docker.sock:ro
# Traefik configuration
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
# Let's Encrypt certificates
- ./letsencrypt:/letsencrypt
# Log files
- ./traefik/logs:/var/log/traefik
networks:
- identity-network
labels:
# Enable Traefik for this container
- "traefik.enable=true"
# Dashboard router
- "traefik.http.routers.traefik.rule=Host(\`traefik.yourdomain.com\`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"
# Protect dashboard with Authentik
- "traefik.http.routers.traefik.middlewares=authentik@docker"
# ----------------------------------------
# POSTGRESQL - Database for Authentik
# ----------------------------------------
postgresql:
image: postgres:16-alpine
container_name: authentik-postgres
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- ./postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS}
POSTGRES_USER: authentik
POSTGRES_DB: authentik
networks:
- identity-network
# ----------------------------------------
# REDIS - Cache & Session Store
# ----------------------------------------
redis:
image: redis:alpine
container_name: authentik-redis
restart: unless-stopped
command: --save 60 1 --loglevel warning
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- ./redis-data:/data
networks:
- identity-network
# ----------------------------------------
# AUTHENTIK SERVER - Identity Provider
# ----------------------------------------
authentik-server:
image: ghcr.io/goauthentik/server:2024.2.2
container_name: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./authentik/media:/media
- ./authentik/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- identity-network
labels:
- "traefik.enable=true"
# Main Authentik router
- "traefik.http.routers.authentik.rule=Host(\`authentik.yourdomain.com\`)"
- "traefik.http.routers.authentik.entrypoints=websecure"
- "traefik.http.routers.authentik.tls.certresolver=letsencrypt"
- "traefik.http.services.authentik.loadbalancer.server.port=9000"
# Forward Auth middleware for protecting other apps
- "traefik.http.middlewares.authentik.forwardauth.address=http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
- "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version"
# ----------------------------------------
# AUTHENTIK WORKER - Background Tasks
# ----------------------------------------
authentik-worker:
image: ghcr.io/goauthentik/server:2024.2.2
container_name: authentik-worker
restart: unless-stopped
command: worker
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./authentik/media:/media
- ./authentik/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- identity-network
# ----------------------------------------
# NETWORKS
# ----------------------------------------
networks:
identity-network:
name: identity-network
driver: bridge
EOF
Replace all instances of yourdomain.com with your actual domain:
# Replace yourdomain.com with your actual domain
# Example: sed -i 's/yourdomain.com/homelab.example.com/g' ...
sed -i 's/yourdomain.com/YOUR_ACTUAL_DOMAIN.com/g' \
~/identity-stack/docker-compose.yml
# Verify the change
grep "Host" ~/identity-stack/docker-compose.yml
# Navigate to project directory
cd ~/identity-stack
# Pull the latest images
docker compose pull
# Start all services in detached mode
# -d = run in background
docker compose up -d
# Watch the logs to monitor startup (Ctrl+C to exit)
docker compose logs -f
Verify all containers are running and healthy:
# Check container status
docker compose ps
# Check for any errors in logs
docker compose logs --tail=20
Allow 2-5 minutes for Let's Encrypt to issue SSL certificates. You can monitor this in the Traefik logs. If you see certificate errors, check the Troubleshooting section.
All core services are now running. In the next phase, you'll configure Authentik through its web interface.
Create admin account, configure flows, and set up MFA.
Open your web browser and navigate to:
https://authentik.yourdomain.com/if/flow/initial-setup/
You should see the Authentik initial setup page. If you see a certificate error, wait a few more minutes for Let's Encrypt to issue the certificate.
Fill in the initial setup form:
| Field | Value | Notes |
|---|---|---|
| Your email address | Used for password recovery | |
| Password | Strong password (16+ characters) | Mix of upper, lower, numbers, symbols |
Click "Create account" to complete initial setup.
After creating your account, you'll be redirected to the user library. Click the "Admin interface" button in the top-right corner to access the administration panel.
You should now be in the Authentik Admin interface. Verify by checking:
Enabling MFA adds a critical security layer. We'll use TOTP (Time-based One-Time Password), compatible with apps like Google Authenticator, Microsoft Authenticator, or Authy.
In the Authentik Admin interface:
Authentik supports additional MFA methods you'd find in enterprise environments:
Authentik is now configured with an admin account and MFA enabled. Your identity provider is ready to protect applications!
Verify the complete setup works end-to-end.
This ensures you're testing a fresh session without cached credentials.
https://authentik.yourdomain.com
cd ~/identity-stack
# Check all containers are healthy
docker compose ps
# Check resource usage
docker stats --no-stream
# Check for any error logs
docker compose logs --tail=50 | grep -i error
# Check certificate status
cat ~/identity-stack/letsencrypt/acme.json | grep -i "domain"
You have successfully deployed a Zero-Trust SSO Gateway! You now have enterprise-grade identity infrastructure running in your home lab.
Below are common issues you may encounter and their solutions.
nslookup authentik.yourdomain.comdocker logs traefik 2>&1 | grep -i acmedocker compose psdocker logs authentik-server --tail=50docker compose restartdocker network ls | grep identitydocker logs authentik-postgrescat .envdocker compose down -v && docker compose up -dsudo ufw allow sshWhen you're done with the lab or need to free up resources, follow these steps to cleanly tear down the environment.
This stops all containers but preserves your data for later use.
cd ~/identity-stack
# Stop all containers (data is preserved)
docker compose stop
# Verify containers are stopped
docker compose ps
Removes containers but keeps database and configuration data.
cd ~/identity-stack
# Stop and remove containers, but keep volumes
docker compose down
# Verify
docker compose ps
This will permanently delete all user data, configurations, and certificates. This action cannot be undone.
cd ~/identity-stack
# Stop and remove everything including volumes
docker compose down -v --remove-orphans
# Remove the project directory
cd ~
rm -rf ~/identity-stack
# Remove unused Docker images (optional)
docker image prune -a
# Remove unused Docker networks (optional)
docker network prune
Congratulations on completing this lab! Here are suggested next steps to continue your IAM learning journey:
Document this project in your portfolio! Include architecture diagrams, screenshots, and a description of the Zero Trust principles you implemented. This demonstrates hands-on experience with IAM technologies that employers actively seek.