Self-host Authentik for SSO in 2026: Docker-compose stack + first integration

Authentik is the open-source identity provider that sits between your apps and
your users. It speaks OIDC, SAML, LDAP, SCIM, RADIUS, and proxy-outpost forward
auth in one binary. As of writing, the current release line is 2026.5.x
(latest tag 2026.5.4, released 2026-07-08), 3-month release cadence, calendar
versioning (YYYY.M.patch). This post walks a fresh Docker stack from zero to
your first protected app, then the four things that will burn you on day two.

What it is, who it’s for

Authentik is an IdP / SSO server built by Jens Langhammer (BeryJu) and a
substantial contributor community. The codebase is mostly Python (Django 5.2 +
DRF + Channels) with a TypeScript / Lit frontend bundled as a standalone SPA,
plus a Go-based proxy outpost for edge deployments. The whole thing is MIT
licensed (with the authentik/enterprise/ directory being a proprietary
add-on for paid support).

Vs the other options in the space:

  • Authelia — single-binary Go forward-auth proxy, narrow scope, no admin
    UI flow editor. Pick it for ~5 users and a handful of apps.
  • Keycloak — JVM stack, more enterprise features (fine-grained authz,
    identity brokering), but heavier to run and older UI. Pick it when you need
    UMA or FAPI conformance.
  • Authentik — modern Lit-based UI with a drag-and-drop flow editor,
    OIDC/SAML/SCIM/LDAP/RADIUS in one box, proxy outposts for legacy apps.
    Pick it for homelab to mid-size self-hosted shops.

The 2026.5 line brought several enterprise-only-tier features down to the
OSS core (JWE for OAuth2, SCIM source property mappings, RADIUS custom
attributes, SAML encryption), so the gap to commercial identity stacks is
notably narrower than it was a year ago.

The Docker stack — compose.yaml

Since 2025.10, Authentik is Postgres-only. Redis (previously used for
caching, the task queue, and WebSocket session storage) is no longer needed.
The default compose file ships three services plus a database volume:

Yaml
services:
  postgresql:
    image: docker.io/library/postgres:16-alpine
    env_file:
      - .env
    environment:
      POSTGRES_DB: ${PG_DB:-authentik}
      POSTGRES_USER: ${PG_USER:-authentik}
      POSTGRES_PASSWORD: ${PG_PASS:?database password required}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - database:/var/lib/postgresql/data
    networks: [auth-net]

  server:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.5.4}
    command: server
    env_file:
      - .env
    ports:
      - "${AUTHENTIK_HTTP_PORT:-9000}:9000"
    depends_on:
      postgresql:
        condition: service_healthy
    volumes:
      - ./data:/data
      - ./custom-templates:/templates
    networks: [auth-net]

  worker:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.5.4}
    command: worker
    depends_on:
      postgresql:
        condition: service_healthy
      server:
        condition: service_started
    env_file:
      - .env
    volumes:
      - ./data:/data
      - ./custom-templates:/templates
      - /var/run/docker.sock:/var/run/docker.sock
    networks: [auth-net]

networks:
  auth-net:
    driver: bridge

volumes:
  database:

Three things that catch people:

  • The server image and worker image are the same. Only the command: server
    vs command: worker differs. Don’t try to swap them.
  • The worker mounts /var/run/docker.sock. It uses this to spawn proxy
    outposts (lightweight Go reverse proxies) on the same Docker network. If
    you’re paranoid about that, run outposts separately and remove the mount.
  • AUTHENTIK_TAG must be a specific version. The :latest tag was frozen
    at 2025.2 back in early 2026; pinning is now mandatory for security
    patching.

The matching .env:

Bash
# Always required
PG_PASS=please-change-me
AUTHENTIK_SECRET_KEY=opensl rand -hex 32
AUTHENTIK_BOOTSTRAP_EMAIL=admin@yourdomain.com
AUTHENTIK_BOOTSTRAP_PASSWORD=please-change-me

# Recommended
AUTHENTIK_TAG=2026.5.4
AUTHENTIK_HTTP_PORT=9000
AUTHENTIK_LOG_LEVEL=info

# Optional: turn off error reporting (default sends stack traces to a hosted Sentry)
AUTHENTIK_ERROR_REPORTING_ENABLED=false

AUTHENTIK_SECRET_KEY is used to sign session tokens and encryption keys — set
it to a 32-byte random hex string. Rotating it invalidates all sessions.

First-run: the initial-setup flow

Authentik ships a built-in initial-setup flow that runs on first browser hit.
The URL must end with a trailing slash:

Bash
http://your-server:9000/if/flow/initial-setup/

A bare http://your-server:9000/if/flow/initial-setup (no trailing slash)
returns “Not Found” — the redirect target is the slash-suffixed path. This
trips people every six months on the project discussion forums.

The flow prompts for an email and password for the default akadmin user,
automatically grants the superuser flag, and logs you in. From there:

  1. Log in as akadmin.
  2. Create a non-superuser user (real you, regular account).
  3. The Application wizard walks you through creating an application + its
    OAuth2/OIDC provider in one go. This is the fastest way to add a new app.
  4. Bind the provider to an authentication flow (or accept the default
    default-authentication-flow).

Recovery, if you lock yourself out: docker compose exec server ak create_recovery_key prints a one-time recovery URL. Default validity is 60
minutes
as of 2025.10 (it used to be years — that was a security footgun).

Wiring up your first app — Nextcloud via OIDC

Most self-hosted Nextcloud setups get here from “I want SSO so my partner
doesn’t have to remember another password.” Concretely:

Bash
# In Authentik admin:
# 1. Applications → Create with provider (wizard)
#    Name: Nextcloud
#    Slug: nextcloud
#    Provider type: OAuth2/OpenID Connect
#    Redirect URI: https://cloud.example.com/apps/user_oidc/oauth/oidc
#    Signing Key: auto-generated
# 2. Save → note the Client ID and Client Secret

Bash
# On the Nextcloud server (via occ or admin UI):
sudo -u www-data php /var/www/nextcloud/occ app:install user_oidc
sudo -u www-data php /var/www/nextcloud/occ config:app:set 
    user_oidc oidc_client_id --value="<from authentik>"
sudo -u www-data php /var/www/nextcloud/occ config:app:set 
    user_oidc oidc_secret --value="<from authentik>"
sudo -u www-data php /var/www/nextcloud/occ config:app:set 
    user_oidc oidc_provider_url 
    --value="https://auth.example.com/application/o/nextcloud/"

A few Nextcloud-specific gotchas from the verified integration docs:

  • HTTPS required. user_oidc refuses to talk to plain-HTTP providers.
  • Trim the client secret to 64 chars. The Nextcloud user_oidc app has a
    bug truncating longer secrets; Authentik generates 80+ char ones by default.
  • Set attribute mapping. On the Authentik provider, map
    nextcloud_user_id to username and nextcloud_quota to a static value
    (or leave unset to inherit from Nextcloud defaults).

Other commonly wired apps (all OIDC unless noted): Gitea (built-in),
Grafana (env vars GF_AUTH_GENERIC_OAUTH_*), Jellyfin (community
OIDC plugin), GitLab (OmniAuth OIDC), OpenVPN Access Server (SAML),
Home Assistant, Portainer, Snipe-IT, Affine, Arcane,
Zammad, Elastic Cloud.

Pitfalls (2026 edition)

A handful of things that bite everyone once:

  • The :latest tag is frozen at 2025.2. Pin a specific version (2026.5.4
    as of writing) in your compose file. If you don’t, you miss every security
    update after 2025.2.
  • Trailing slash on the initial-setup URL. Without it you get 404. Add it,
    bookmark it.
  • *`AUTHENTIKBOOTSTRAPenv vars are silently ignored if you mount an empty/blueprints` directory.** This is the active wontfix/legacy issue

    7546. Either include at least one blueprint, or use the browser-based

    initial flow.

  • Recovery keys only last 60 minutes as of 2025.10. Save the URL
    immediately, don’t store it in a 1Password note you only check next week.
  • LDAP TLS verification has an opt-in “TLS Verification Certificate”
    field.
    Leave it blank to skip cert validation (insecure default). For
    real verification, point it at your CA bundle.
  • Outpost Docker socket. The worker uses /var/run/docker.sock to spawn
    proxy outposts automatically. If that mount worries you, deploy outposts
    manually with the standalone ghcr.io/goauthentik/proxy image and remove
    the socket mount from the worker.
  • Calendar versioning, not semver. 2026.5.4 is not the same as
    upstream SemVer 5.4. Major-version migrations between YYYY.M series
    sometimes require reading the release notes.

When to pick something else

The rough rule of thumb for self-hosted SSO in 2026:

Need Pick
1-10 apps, single user/partner, no flow customization Authelia
OIDC + SAML + SCIM + LDAP/RADIUS + custom flows + homelab UI polish Authentik
Hundreds of users, fine-grained authorization, identity brokering, FAPI/UMA Keycloak
Enterprise SSO with no self-hosting Microsoft Entra / Okta

For a single Nextcloud at home, Authelia or even oauth2-proxy in front of
Nextcloud is plenty. You bring out Authentik when you have 3+ apps that all
need their own user store, or you need to enforce MFA centrally.

TL;DR

Bash
mkdir ~/authentik && cd $_
curl -fsSL https://docs.goauthentik.io/compose.yaml > compose.yaml
curl -fsSL https://docs.goauthentik.io/.env > .env
$EDITOR .env  # set PG_PASS, AUTHENTIK_SECRET_KEY, BOOTSTRAP_EMAIL, BOOTSTRAP_PASSWORD
docker compose up -d

# Visit http://your-server:9000/if/flow/initial-setup/  (NOTE trailing slash)

That’s it. Browser → set admin password → add your first Application via
wizard → done.

Sources

Last modified: 2026年7月16日

Author

Comments

Write a Reply or Comment

Your email address will not be published.