The stack at a glance

Authentik is a Django app plus a PostgreSQL database plus Redis plus one or more "outposts" — small Go binaries that handle the actual proxy/LDAP/RADIUS endpoints. The recommended deployment is a docker compose stack of: server (web UI + API), worker (background tasks), postgresql, redis. Outposts can be embedded (in the main server container) or deployed separately.

docker compose setup

Create a directory for the stack, drop in a docker-compose.yml, an .env with secrets, and the official media and certs volume mounts:

# docker-compose.yml
version: "3.9"

services:
  postgresql:
    image: docker.io/library/postgres:16-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "${PG_USER:-authentik}"]
      interval: 30s
      timeout: 5s
      retries: 5
    volumes:
      - database:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${PG_PASS}
      POSTGRES_USER: ${PG_USER:-authentik}
      POSTGRES_DB:   ${PG_DB:-authentik}

  redis:
    image: docker.io/library/redis:alpine
    command: --save 60 1 --loglevel warning
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      interval: 30s
      timeout: 3s
      retries: 5
    volumes:
      - redis:/data

  server:
    image: ghcr.io/goauthentik/server:2026.4
    restart: unless-stopped
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
      AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
    volumes:
      - ./media:/media
      - ./custom-templates:/templates
    ports:
      - "9000:9000"
      - "9443:9443"
    depends_on:
      - postgresql
      - redis

  worker:
    image: ghcr.io/goauthentik/server:2026.4
    restart: unless-stopped
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
    user: root
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./media:/media
      - ./certs:/certs
      - ./custom-templates:/templates
    depends_on:
      - postgresql
      - redis

volumes:
  database:
  redis:
# .env
PG_PASS=$(openssl rand -base64 36 | tr -d '\n')
AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n')

Generate those two secrets ahead of time (they'd be templated into the file by hand). Then:

docker compose up -d
docker compose logs -f server

The server is now listening on http://localhost:9000 (HTTP) and https://localhost:9443 (HTTPS with a self-signed cert). The first run also boots a bootstrap flow at /if/flow/initial-setup/ — visit that path to create the admin user.

Don't expose 9000/9443 directly

Put Authentik behind your reverse proxy (Caddy, nginx, Traefik) for TLS termination with a real cert. The Authentik server expects the proxy to set X-Forwarded-For and X-Forwarded-Proto; both nginx recommendedProxySettings and Caddy's reverse_proxy do this by default.

Initial admin setup

Browse to /if/flow/initial-setup/. Create the akadmin user, set its password, and you're logged into the admin UI at /if/admin/. Two things to do first:

  1. Set the public URL. System → Configuration. The default is http://localhost:9000; change it to the URL the proxy serves Authentik on (e.g. https://auth.example.com). This is what OIDC discovery URLs and email links will reference.
  2. Email. System → Configuration → SMTP server. Without email, password reset and email-based MFA enrollment don't work.

Concept: providers, applications, outposts

Authentik separates three things that other IdPs conflate:

  • Provider — the protocol-level configuration. An OIDC provider has client-id / client-secret / redirect URIs / scopes. A proxy provider has external host, internal host, mode. A SAML provider has metadata, signing certs.
  • Application — what users see in the Authentik app launcher; ties a slug and an icon to one provider, and applies policy bindings for who can access it.
  • Outpost — for proxy/LDAP/RADIUS providers, the actual binary that does the protocol. By default, an "embedded" outpost runs inside the server container; for production, separate outposts can be deployed near the apps they protect.

Adding an OIDC app (example: Grafana)

In the admin UI:

  1. Applications → Providers → Create → OAuth2/OpenID Provider.
    • Name: grafana
    • Client type: Confidential
    • Client ID, Client Secret: auto-generated — copy them, you'll need them in grafana.ini
    • Redirect URIs: https://grafana.example.com/login/generic_oauth
    • Signing Key: pick the default certificate
  2. Applications → Applications → Create.
    • Name: Grafana
    • Slug: grafana
    • Provider: the grafana provider just created
    • Launch URL: https://grafana.example.com

In Grafana's grafana.ini (or its env-var equivalents):

[auth.generic_oauth]
enabled = true
name = Authentik
client_id = <client-id>
client_secret = <client-secret>
scopes = openid email profile
auth_url   = https://auth.example.com/application/o/authorize/
token_url  = https://auth.example.com/application/o/token/
api_url    = https://auth.example.com/application/o/userinfo/
role_attribute_path = contains(groups[*], 'grafana-admins') && 'Admin' || contains(groups[*], 'grafana-editors') && 'Editor' || 'Viewer'

Restart Grafana. The login page now shows a "Sign in with Authentik" button.

Forward-auth for apps without OIDC

Some self-hosted apps don't speak OIDC. The forward-auth pattern lets the reverse proxy ask Authentik "is this user allowed through?" before serving the request:

  1. Create a provider: Applications → Providers → Create → Proxy Provider.
    • Mode: Forward auth (single application) — for one external host — or Forward auth (domain level) for a wildcard.
    • External host: https://app.example.com
  2. Create an application bound to it.
  3. Outposts → Edit the embedded outpost → assign the new provider to it. Within a few seconds, the outpost's /outpost.goauthentik.io/ endpoints are live on the Authentik server.

On the Caddy side:

app.example.com {
    forward_auth auth.example.com {
        uri /outpost.goauthentik.io/auth/caddy
        copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
        trusted_proxies private_ranges
    }

    reverse_proxy 127.0.0.1:8080
}

auth.example.com {
    reverse_proxy 127.0.0.1:9000
}

Now any request to app.example.com is bounced through Authentik first; on success, the proxy adds X-Authentik-* headers the upstream can trust (because the proxy is the one setting them).

Backups

Two things matter:

  • PostgreSQL — all users, providers, applications, policies, flows. docker exec postgresql pg_dump -U authentik authentik | gzip > backup-$(date +%F).sql.gz on a nightly timer.
  • Media volume — uploaded icons, custom templates, branding. Snapshot it alongside the database dump.

The AUTHENTIK_SECRET_KEY from .env is required to decrypt fields encrypted at rest, so back it up out of band with the rest of the secrets.

What to read after this

The official docs are thorough; the most useful pages once the basics work are:

  • Flows & Stages — how login/enrollment/password-reset are composed from reusable stages, and how to add MFA enrollment or geo-restrictions.
  • Policies — group-based access control with Python expressions as the escape hatch.
  • Property mappings — transform attributes between Authentik and the downstream app (e.g. derive Grafana roles from group membership).