Notes on getting authentik working
A collection of notes and fixes from setting up authentik as an identity provider with Active Directory LDAP sync, Traefik forward auth, and Docker Swarm. The two main gotchas: AD syncs computer objects as users by default, and group sync fails out of the box with a cryptic “run sync_groups first” error. Below are the fixes and a complete Docker Swarm playground to test it all.
AD sync fixes
set User object filter to exclude computers
(&(objectClass=user)(!(objectClass=computer)))
My groups were not syncing — authentik kept erroring with “Group does not exist in our DB yet, run sync_groups first.” This comment on GitHub issue #11427 had the fix: change the group property mapping to use only “authentik default LDAP Mapping: Name” and set the object uniqueness field to “objectSid”.

My swarm demo playground
version: "3.8"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: authentik_db_password
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
deploy:
replicas: 1
redis:
image: redis:7-alpine
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
volumes:
- redis_data:/data
networks:
- backend
deploy:
replicas: 1
server:
image: ghcr.io/goauthentik/server:2025.2.1
command: server
environment: &authentik-env
AUTHENTIK_SECRET_KEY: ""
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: authentik_db_password
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_BOOTSTRAP_PASSWORD: "admin"
AUTHENTIK_BOOTSTRAP_TOKEN: "admin-bootstrap-token"
AUTHENTIK_BOOTSTRAP_EMAIL: "[email protected]"
networks:
- proxy
- backend
deploy:
replicas: 1
labels:
- "traefik.enable=true"
# Authentik UI/API
- "traefik.http.routers.authentik.rule=Host(`auth.localhost`)"
- "traefik.http.routers.authentik.entrypoints=websecure"
- "traefik.http.routers.authentik.tls=true"
- "traefik.http.services.authentik.loadbalancer.server.port=9000"
# Embedded outpost: handles /outpost.goauthentik.io on app.localhost
- "traefik.http.routers.authentik-outpost.rule=Host(`app.localhost`) && PathPrefix(`/outpost.goauthentik.io`)"
- "traefik.http.routers.authentik-outpost.entrypoints=websecure"
- "traefik.http.routers.authentik-outpost.tls=true"
- "traefik.http.routers.authentik-outpost.service=authentik"
# Reusable ForwardAuth middleware (points to embedded outpost on the server)
- "traefik.http.middlewares.authentik-auth.forwardauth.address=http://authentik_server:9000/outpost.goauthentik.io/auth/traefik"
- "traefik.http.middlewares.authentik-auth.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authentik-auth.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid"
worker:
image: ghcr.io/goauthentik/server:2025.2.1
command: worker
environment: *authentik-env
networks:
- backend
deploy:
replicas: 1
volumes:
postgres_data:
redis_data:
networks:
proxy:
external: true
name: shared_proxy
backend:
external: true
name: shared_backend
My demo app
version: "3.8"
services:
demo-app:
image: traefik/whoami:latest
networks:
- proxy
deploy:
replicas: 1
labels:
- "traefik.enable=true"
- "traefik.http.routers.demo-app.rule=Host(`app.localhost`)"
- "traefik.http.routers.demo-app.entrypoints=websecure"
- "traefik.http.routers.demo-app.tls=true"
- "traefik.http.routers.demo-app.middlewares=authentik-auth@swarm"
- "traefik.http.services.demo-app.loadbalancer.server.port=80"
networks:
proxy:
external: true
name: shared_proxy
Traefik (Swarm mode – for plain Docker Compose, use providers.docker instead of providers.swarm, move labels from deploy to the service directly, and reference middlewares as @docker instead of @swarm)
version: "3.8"
services:
traefik:
image: traefik:v3.6
command:
- "--api.dashboard=true"
- "--api.insecure=true"
- "--providers.swarm.endpoint=unix:///var/run/docker.sock"
- "--providers.swarm.exposedByDefault=false"
- "--providers.swarm.network=shared_proxy"
- "--providers.file.filename=/etc/traefik/dynamic.yml"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--log.level=INFO"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
configs:
- source: traefik_dynamic
target: /etc/traefik/dynamic.yml
- source: tls_cert
target: /certs/cert.pem
- source: tls_key
target: /certs/key.pem
networks:
- proxy
deploy:
placement:
constraints:
- node.role == manager
configs:
traefik_dynamic:
file: ./dynamic.yml
tls_cert:
file: ./certs/cert.pem
tls_key:
file: ./certs/key.pem
networks:
proxy:
external: true
name: shared_proxy
OpenLDAP demo
version: "3.8"
services:
openldap:
image: osixia/openldap:1.5.0
environment:
LDAP_ORGANISATION: "Demo Corp"
LDAP_DOMAIN: "demo.local"
LDAP_BASE_DN: "dc=demo,dc=local"
LDAP_ADMIN_PASSWORD: "admin_password"
LDAP_READONLY_USER: "true"
LDAP_READONLY_USER_USERNAME: "readonly"
LDAP_READONLY_USER_PASSWORD: "readonly_password"
volumes:
- ldap_data:/var/lib/ldap
- ldap_config:/etc/ldap/slapd.d
networks:
default:
aliases:
- ldap
deploy:
replicas: 1
ldap-seed:
image: osixia/openldap:1.5.0
entrypoint: /bin/bash
command:
- -c
- |
for i in $$(seq 1 30); do
ldapsearch -x -H ldap://openldap -D "cn=admin,dc=demo,dc=local" -w admin_password -b "dc=demo,dc=local" > /dev/null 2>&1 && break
echo "Waiting for LDAP... ($$i/30)"
sleep 2
done
ldapsearch -x -H ldap://openldap -D "cn=admin,dc=demo,dc=local" -w admin_password -b "ou=users,dc=demo,dc=local" > /dev/null 2>&1
if [ $$? -eq 0 ]; then
echo "LDAP already seeded, skipping."
exit 0
fi
echo "Seeding LDAP with demo accounts..."
ldapadd -x -H ldap://openldap -D "cn=admin,dc=demo,dc=local" -w admin_password <<'LDIF'
dn: ou=users,dc=demo,dc=local
objectClass: organizationalUnit
ou: users
dn: ou=groups,dc=demo,dc=local
objectClass: organizationalUnit
ou: groups
dn: uid=alice,ou=users,dc=demo,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Alice Johnson
sn: Johnson
givenName: Alice
uid: alice
uidNumber: 1001
gidNumber: 1001
mail: [email protected]
homeDirectory: /home/alice
userPassword: alice123
dn: uid=bob,ou=users,dc=demo,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Bob Smith
sn: Smith
givenName: Bob
uid: bob
uidNumber: 1002
gidNumber: 1001
mail: [email protected]
homeDirectory: /home/bob
userPassword: bob123
dn: uid=charlie,ou=users,dc=demo,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Charlie Brown
sn: Brown
givenName: Charlie
uid: charlie
uidNumber: 1003
gidNumber: 1001
mail: [email protected]
homeDirectory: /home/charlie
userPassword: charlie123
dn: cn=developers,ou=groups,dc=demo,dc=local
objectClass: groupOfNames
cn: developers
member: uid=alice,ou=users,dc=demo,dc=local
member: uid=bob,ou=users,dc=demo,dc=local
dn: cn=admins,ou=groups,dc=demo,dc=local
objectClass: groupOfNames
cn: admins
member: uid=alice,ou=users,dc=demo,dc=local
LDIF
echo "LDAP seeding complete."
deploy:
restart_policy:
condition: on-failure
max_attempts: 5
volumes:
ldap_data:
ldap_config:
networks:
default:
external: true
name: shared_backend