Traefik with Pocket ID
Traefik is a modern HTTP reverse proxy and load balancer integrated with Pocket ID for passkey-first SSO. It dynamically routes web traffic, manages SSL certificates and enforces centralized identity protection across all self-hosted services via OIDC and forward authentication.
References
Directory structure
Directorytraefik
Directoryconfig
- traefik.yml (*)
- dynamic_config.yml (*)
Directoryletsencrypt
- acme.json
- compose.yml (*)
Make directory
mkdir -p {{DOCKER_PATH_VAR}}/traefik/config {{DOCKER_PATH_VAR}}/traefik/letsencrypt {{DOCKER_PATH_VAR}}/traefik/pocket-id-data && cd {{DOCKER_PATH_VAR}}/traefikcompose.yml
nano compose.ymlservices: traefik: container_name: traefik image: traefik:v3.7 ports: - "80:80" - "443:443" environment: CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN} volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./config/traefik:/etc/traefik:ro - ./letsencrypt:/letsencrypt restart: unless-stopped
pocket-id: container_name: pocket-id image: pocketid/pocket-id:v2 env_file: .env ports: - 1411:1411 volumes: - "./pocket-id-data:/app/data" restart: unless-stopped
traefik-forward-auth: container_name: traefik-forward-auth image: ghcr.io/italypaleale/traefik-forward-auth:4 environment: DEFAULT_PROVIDER: ${DEFAULT_PROVIDER} PROVIDERS_OIDC_ISSUER_URL: ${PROVIDERS_OIDC_ISSUER_URL} PROVIDERS_OIDC_CLIENT_ID: ${PROVIDERS_OIDC_CLIENT_ID} PROVIDERS_OIDC_CLIENT_SECRET: ${PROVIDERS_OIDC_CLIENT_SECRET} SECRET: ${SECRET} COOKIE_DOMAIN: ${COOKIE_DOMAIN} AUTH_HOST: ${AUTH_HOST} restart: unless-stoppedtraefik.yml
nano ./config/traefik.ymlentryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443" transport: respondingTimeouts: readTimeout: "30m" http: tls: certResolver: "cloudflare"
providers: docker: exposedByDefault: false file: filename: "/etc/traefik/dynamic_config.yml" watch: true
certificatesResolvers: cloudflare: acme: storage: "/letsencrypt/acme.json" caServer: "https://acme-staging-v02.api.letsencrypt.org/directory" # staging, remove for production dnsChallenge: provider: "cloudflare" resolvers: - "1.1.1.1:53" - "1.0.0.1:53"
api: insecure: false dashboard: true
log: level: "INFO" format: "common"dynamic_config.yml
nano ./config/dynamic_config.ymlhttp: middlewares: pocket-id-auth: forwardAuth: address: "http://traefik-forward-auth:4181" authResponseHeaders: - "X-Forwarded-User" - "X-Forwarded-Email" trustForwardHeader: true
hsts-header: headers: stsSeconds: 31536000 stsIncludeSubdomains: true
routers: router-pocket-id: rule: "Host(`auth.{{BASE_SUB_DOMAIN_VAR}}`)" service: service-pocket-id entryPoints: ["websecure"]
router-dashboard: rule: "Host(`traefik.{{BASE_SUB_DOMAIN_VAR}}`)" service: api@internal entryPoints: ["websecure"] middlewares: ["pocket-id-auth"]
router-adguard: rule: "Host(`adguard.{{BASE_SUB_DOMAIN_VAR}}`)" service: service-adguard entryPoints: ["websecure"] middlewares: ["pocket-id-auth"]
router-arcane: rule: "Host(`arcane.{{BASE_SUB_DOMAIN_VAR}}`)" service: service-arcane entryPoints: ["websecure"] middlewares: ["hsts-header"]
router-echo-link: rule: "Host(`echo.{{BASE_SUB_DOMAIN_VAR}}`)" service: service-echo-link entryPoints: ["websecure"]
router-immich: rule: "Host(`immich.{{BASE_SUB_DOMAIN_VAR}}`)" service: service-immich entryPoints: ["websecure"]
router-jellyfin: rule: "Host(`jf.{{BASE_SUB_DOMAIN_VAR}}`)" service: service-jellyfin entryPoints: ["websecure"]
router-linkding: rule: "Host(`link.{{BASE_SUB_DOMAIN_VAR}}`)" service: service-linkding entryPoints: ["websecure"]
router-vaultwarden: rule: "Host(`vw.{{BASE_SUB_DOMAIN_VAR}}`)" service: service-vaultwarden entryPoints: ["websecure"] middlewares: ["hsts-header"]
services: service-pocket-id: loadBalancer: servers: - url: "http://pocketid:1411"
service-adguard: loadBalancer: servers: - url: "{{SERVER_IP_VAR}}:8080"
service-arcane: loadBalancer: servers: - url: "{{SERVER_IP_VAR}}:3552"
service-echo-link: loadBalancer: servers: - url: "{{SERVER_IP_VAR}}:3002"
service-immich: loadBalancer: servers: - url: "{{SERVER_IP_VAR}}:2283"
service-jellyfin: loadBalancer: servers: - url: "{{SERVER_IP_VAR}}:8096"
service-linkding: loadBalancer: servers: - url: "{{SERVER_IP_VAR}}:9090"
service-vaultwarden: loadBalancer: servers: - url: "{{SERVER_IP_VAR}}:8002"acme.json
touch ./letsencrypt/acme.json && chmod 600 ./letsencrypt/acme.jsonCloudflare DNS API token
.env
nano .env# pocket-idAPP_URL=https://auth.{{BASE_SUB_DOMAIN_VAR}}
# Encryption key (choose one method):# Method 1: Direct key (simple but less secure)# Generate with: openssl rand -base64 32ENCRYPTION_KEY=# Method 2: File-based key (recommended)# Put the base64 key in a file and point to it here.# ENCRYPTION_KEY_FILE=/path/to/encryption_key
TRUST_PROXY=truePUID=1000PGID=1000
# traefikCF_DNS_API_TOKEN=YOUR_TOKEN
# traefik-forward-authDEFAULT_PROVIDER=oidcPROVIDERS_OIDC_ISSUER_URL=https://auth.{{BASE_SUB_DOMAIN_VAR}}PROVIDERS_OIDC_CLIENT_ID=$FORWARD_AUTH_CLIENT_IDPROVIDERS_OIDC_CLIENT_SECRET=$FORWARD_AUTH_CLIENT_SECRETSECRET=$YOUR_FORWARD_AUTH_SESSION_SECRET # openssl rand -hex 16COOKIE_DOMAIN={{BASE_SUB_DOMAIN_VAR}}AUTH_HOST=auth.{{BASE_SUB_DOMAIN_VAR}}Start pocket-id and traefik
docker compose up -d pocket-id traefikOpen web setup
- Open https://auth.{{BASE_SUB_DOMAIN_VAR}}/setup in your browser.
- Set up your admin account and register your primary Passkey.
- Go to Administration > OIDC Clients > Add OIDC Client:
- Name:
Traefik Forward Auth - Callback URL:
https://auth.{{BASE_SUB_DOMAIN_VAR}}/_oauth - Check
Public Clientif secret is not enforced, or copy theClient IDandClient Secret.
- Name:
- Paste those credentials into the
PROVIDERS_OIDC_CLIENT_IDandPROVIDERS_OIDC_CLIENT_SECRETenvironment variables in.env.
Start traefik-forward-auth
docker compose up -d traefik-forward-auth