Skip to content

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

Terminal window
mkdir -p {{DOCKER_PATH_VAR}}/traefik/config {{DOCKER_PATH_VAR}}/traefik/letsencrypt {{DOCKER_PATH_VAR}}/traefik/pocket-id-data && cd {{DOCKER_PATH_VAR}}/traefik

compose.yml

Terminal window
nano compose.yml
compose.yml
services:
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-stopped

traefik.yml

Terminal window
nano ./config/traefik.yml
./config/traefik.yml
entryPoints:
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:
email: admin@example.com
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

Terminal window
nano ./config/dynamic_config.yml
./config/dynamic_config.yml
http:
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

Terminal window
touch ./letsencrypt/acme.json && chmod 600 ./letsencrypt/acme.json

Cloudflare DNS API token

.env

Terminal window
nano .env
.env
# pocket-id
APP_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 32
ENCRYPTION_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=true
PUID=1000
PGID=1000
# traefik
CF_DNS_API_TOKEN=YOUR_TOKEN
# traefik-forward-auth
DEFAULT_PROVIDER=oidc
PROVIDERS_OIDC_ISSUER_URL=https://auth.{{BASE_SUB_DOMAIN_VAR}}
PROVIDERS_OIDC_CLIENT_ID=$FORWARD_AUTH_CLIENT_ID
PROVIDERS_OIDC_CLIENT_SECRET=$FORWARD_AUTH_CLIENT_SECRET
SECRET=$YOUR_FORWARD_AUTH_SESSION_SECRET # openssl rand -hex 16
COOKIE_DOMAIN={{BASE_SUB_DOMAIN_VAR}}
AUTH_HOST=auth.{{BASE_SUB_DOMAIN_VAR}}

Start pocket-id and traefik

Terminal window
docker compose up -d pocket-id traefik

Open web setup

  1. Open https://auth.{{BASE_SUB_DOMAIN_VAR}}/setup in your browser.
  2. Set up your admin account and register your primary Passkey.
  3. Go to Administration > OIDC Clients > Add OIDC Client:
    • Name: Traefik Forward Auth
    • Callback URL: https://auth.{{BASE_SUB_DOMAIN_VAR}}/_oauth
    • Check Public Client if secret is not enforced, or copy the Client ID and Client Secret.
  4. Paste those credentials into the PROVIDERS_OIDC_CLIENT_ID and PROVIDERS_OIDC_CLIENT_SECRET environment variables in .env.

Start traefik-forward-auth

Terminal window
docker compose up -d traefik-forward-auth