Auto Draft

Self-Hosting Authentik on Docker: Add SSO and Identity Management to Your Homelab

What Is Authentik and Why Does Your Homelab Need It?

If you’re running more than a handful of self-hosted services, you’ve probably noticed the password problem: every app has its own login page, its own user database, and its own approach to access control. Grafana wants one username, Nextcloud wants another, and Portainer does its own thing entirely. Add a second household member or a colleague and the sprawl gets painful fast — resetting passwords, keeping user lists in sync, remembering which app uses which email address.

Authentik solves this. It’s an open-source identity provider (IdP) that gives every app in your homelab a single sign-on (SSO) interface. Log in once, and you’re authenticated everywhere. It supports OAuth2/OIDC, SAML, LDAP, and SCIM out of the box, meaning it speaks the language of virtually every modern self-hosted application. And because it runs entirely in Docker, dropping it into an existing stack takes about 20 minutes of real work.

If you’re already comfortable with Docker — and if you worked through our complete Docker guide you should be — then Authentik is the most impactful upgrade you can make to your homelab security posture this year.

Authentik vs. the Alternatives

A few options exist in the self-hosted IdP space, so it’s worth knowing where Authentik fits:

  • Authelia — Lightweight, fast to set up, great for simple forward-auth scenarios. Doesn’t support full OAuth2/OIDC or SAML for apps that need them as identity providers. If all you need is a login wall in front of a few apps, Authelia is simpler. If you want real SSO with user directories and protocol flexibility, it falls short.
  • Keycloak — Enterprise-grade, extremely feature-rich, the gold standard for production deployments. Also runs on Java, consumes 512 MB+ of RAM at idle, and has a steep configuration learning curve. Overkill for most homelabs.
  • Authentik — Python/Django-based, modern web UI, supports every relevant protocol, includes a built-in Proxy Outpost for apps without native auth. Around 400–500 MB RAM at idle. This is the sweet spot for serious homelab operators.

How Authentik Works: A Quick Mental Model

Authentik sits between your users and your applications. When a user visits a protected service, one of two things happens:

  1. Native protocol — The app redirects the user to Authentik’s login page via OAuth2 or SAML. After authentication, Authentik issues a token that the app validates and trusts. The app never handles the password.
  2. Proxy Outpost — For apps with no built-in auth support, Authentik’s proxy worker intercepts all requests via your reverse proxy’s forward-auth mechanism. Unauthenticated requests get redirected to the Authentik login page; authenticated sessions pass through transparently.

Inside Authentik, four concepts do most of the work:

  • Flows — Step-by-step sequences that define what happens during login, enrollment, or password recovery. You can insert MFA prompts, email verification, or custom approval stages anywhere in the flow.
  • Providers — Protocol adapters. Each application you protect gets its own provider (OAuth2/OIDC, SAML, Proxy, or LDAP).
  • Applications — The logical representation of each service. An application ties a provider to access policy rules and determines who can log in.
  • Outposts — Lightweight worker processes. The embedded outpost handles proxy auth; external outposts can run closer to your apps for lower latency.

Prerequisites

  • A Linux host running Docker and Docker Compose v2+
  • A domain name or local DNS entry pointing to your homelab (e.g., auth.home.lab or a real subdomain)
  • A reverse proxy already in place (Nginx Proxy Manager, Traefik, or Caddy)
  • Approximately 2 GB of available RAM for the full Authentik stack
  • An SMTP server or relay for email (enrollment confirmations, password resets)

Step 1: Generate Secrets and Prepare the Environment

Create a directory for Authentik and generate cryptographically random secrets. Never hardcode these:

mkdir -p ~/docker/authentik && cd ~/docker/authentik

PG_PASS=$(openssl rand -base64 36 | tr -d '\n')
AUTHENTIK_SECRET=$(openssl rand -base64 60 | tr -d '\n')

cat > .env <

The chmod 600 is important — this file contains your database password and secret key. Don't commit it to Git, and don't leave it world-readable.

Step 2: Write the Docker Compose File

cat > docker-compose.yml <<'EOF'
version: "3.9"

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

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

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

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

volumes:
  database:
  redis:
EOF

Design decisions in this compose file worth understanding:

  • Pinned image tag (2024.12.1) — Never use latest for a service this critical. Pinning gives you reproducible deployments and protects against surprise breaking changes during automatic updates.
  • Health checks with conditions — The server and worker only start after PostgreSQL passes its health check. This prevents startup failures caused by race conditions on slower hardware.
  • Worker runs as root — Required for Docker socket access (Outpost management). If that's a concern, Authentik docs describe a rootless alternative using a dedicated socket proxy.
  • Separate server and worker — The server handles web requests; the worker handles background tasks (email sending, scheduled tasks, Outpost management). Keeping them separate lets you scale them independently.

Step 3: Initial Setup

docker compose up -d

# Follow server logs until it's ready
docker compose logs -f server 2>&1 | grep -E "Starting web server|ERROR"

First boot takes 2–3 minutes as the database schema is created and migrations run. When the server reports it's listening, navigate to:

http://<your-host-ip>:9000/if/flow/initial-setup/

Set your admin email and password. This URL works exactly once — after initial setup it returns a 404. Write down these credentials; if you lose them you'll need to reset via the database.

Once logged into the admin UI (/if/admin/), the first thing to do is create your user accounts under Directory → Users and set up groups under Directory → Groups. Groups are how you control which users can access which applications.

Step 4: Protecting Nextcloud with OAuth2/OIDC

Nextcloud has excellent native OAuth2 support and makes a perfect first integration. If you already have Nextcloud running, here's the full wiring.

Create the Provider in Authentik

Navigate to Applications → Providers → Create → OAuth2/OpenID Provider:

  • Name: Nextcloud OAuth2
  • Client type: Confidential
  • Client ID: Auto-generated — copy it
  • Client Secret: Auto-generated — copy it
  • Redirect URIs/Origins: https://nextcloud.yourdomain.com/apps/social_login/custom_oidc/authentik
  • Signing key: authentik Self-signed Certificate
  • Scopes: openid, email, profile (defaults)

Then create an Application named "Nextcloud", link it to this provider, and set the Launch URL to your Nextcloud URL. Under Policy/Group Bindings, restrict access to a specific group if you want — e.g., a "homelab-users" group.

Configure Nextcloud

Install the Social Login app via the Nextcloud App Store. In Settings → Social Login, add a Custom OpenID Connect entry:

  • Internal name: authentik
  • Title: Sign in with SSO
  • Discovery URL: https://auth.yourdomain.com/application/o/nextcloud/.well-known/openid-configuration
  • Client ID / Secret: from the Authentik provider
  • Button style: your choice

Enable "Update user profile on login" so display names and email addresses stay in sync. Save, log out, and your Nextcloud login page will now show a "Sign in with SSO" button backed by Authentik.

Step 5: The Proxy Outpost — SSO for Apps Without Native Auth

The Proxy Outpost is where Authentik really earns its keep. Tools like Uptime Kuma (see our Uptime Kuma monitoring guide), plain dashboards, or any app you built yourself with no authentication layer can be protected behind Authentik with minimal configuration.

Create a Proxy Provider

Go to Providers → Create → Proxy Provider:

  • Name: Uptime Kuma Proxy
  • Forward auth (single application) — this mode adds auth to a specific URL
  • External Host: https://uptime.yourdomain.com

Create the corresponding Application, then navigate to Outposts → authentik Embedded Outpost → Edit and add your new application to the outpost's application list. The embedded outpost picks up changes immediately — no restart needed.

Nginx Proxy Manager Forward Auth Configuration

In Nginx Proxy Manager, for your Uptime Kuma proxy host, paste the following under Advanced → Custom Nginx Configuration:

location /outpost.goauthentik.io {
    proxy_pass          https://your-authentik-host:9443/outpost.goauthentik.io;
    proxy_http_version  1.1;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
    proxy_set_header    X-Original-URL $scheme://$http_host$request_uri;
    add_header          Set-Cookie $auth_cookie;
    auth_request_set    $auth_cookie $upstream_http_set_cookie;
}

auth_request        /outpost.goauthentik.io/auth/nginx;
error_page 401 =    @goauthentik_proxy_signin;
auth_request_set    $auth_header $upstream_http_x_authentik_username;

location @goauthentik_proxy_signin {
    internal;
    add_header Set-Cookie $auth_cookie;
    return 302 /outpost.goauthentik.io/start?rd=$request_uri;
}

After saving, any unauthenticated request to your Uptime Kuma subdomain will redirect to your Authentik login page. Post-login, the user is returned to their original destination. The app itself is completely unaware this is happening.

Step 6: Enforcing Multi-Factor Authentication

Authentik ships with TOTP (compatible with any authenticator app) and WebAuthn (hardware keys like YubiKey). Enabling MFA globally takes about two minutes:

  1. Navigate to Flows → default-authentication-flow
  2. Click Stage Bindings → Bind existing stage
  3. Select default-mfa-validation, set Order to 30 (after password at order 20), and enable "Evaluate when flow is planned"

Users who haven't enrolled an authenticator will be prompted to do so on their next login. You can also scope MFA to specific high-risk applications rather than applying it globally — useful for a homelab where you want friction on your VPN console but not on your media server.

For hardware keys, navigate to Flows → default-user-settings-flow → Stage Bindings and add the default-authenticator-webauthn-setup stage. Users can then register their security key from their user profile settings page.

Maintenance: Upgrades and Backups

Upgrading Authentik

# Update the image tag in docker-compose.yml to the new version, then:
docker compose pull
docker compose up -d

# Authentik runs database migrations automatically on startup
docker compose logs -f server | grep -E "migrat|Starting web server"

Always read the release notes before upgrading. Authentik follows a structured release cadence and clearly flags breaking changes. Going more than two minor versions in a single jump is not recommended — step through them sequentially if you've fallen behind.

Backing Up Authentik

# PostgreSQL dump (run this from the host, not inside the container)
docker exec authentik-postgresql-1 \
  pg_dump -U authentik authentik \
  > /backups/authentik_db_$(date +%Y%m%d_%H%M).sql

# Back up media (uploaded profile photos, icons, custom templates)
tar czf /backups/authentik_media_$(date +%Y%m%d).tar.gz \
  ~/docker/authentik/media/ \
  ~/docker/authentik/custom-templates/

Automate this with a cron job or a scheduled Ansible task. Store the backups off-host — on a NAS, an S3-compatible bucket, or another machine on your network. The database dump is small (typically under 5 MB for a homelab deployment), so there's no reason not to keep 30 days of history.

Troubleshooting Common Issues

Login Loop / Redirect Not Completing

Almost always a misconfigured Redirect URI. The URI in the OAuth2 provider must exactly match — character for character — what the application sends in its authorization request. Including or omitting a trailing slash can cause a mismatch. Check server logs for the specific error:

docker compose logs server | grep -i "redirect_uri"

Proxy Outpost Not Intercepting Requests

# Verify the outpost is connected
docker compose logs worker | grep -i outpost

# Test that the server's health endpoint is reachable from the worker
docker exec authentik-worker-1 \
  curl -sk https://authentik-server:9443/-/health/ready/ && echo "OK"

If the worker can't reach the server, check that both containers are on the same Docker network and that your firewall isn't blocking inter-container traffic.

Users Can't Enroll TOTP

Verify that the default-authenticator-totp-setup stage is bound to your default-user-settings-flow. Navigate to Flows → default-user-settings-flow → Stage Bindings and confirm the stage is present and enabled. If users are being skipped past MFA enrollment, check your flow policy — a policy bound to the MFA stage that evaluates to "skip" for certain users will silently bypass enrollment.

Where to Go From Here

Once the core stack is running and you've integrated two or three apps, the next steps are:

  • LDAP Outpost — Spin up an LDAP outpost to support apps that only speak directory protocols (older tools, network devices, etc.). Authentik acts as an LDAP server, and apps query it exactly as they would Active Directory or OpenLDAP.
  • Grafana SSO — Authentik ships a pre-built OAuth2 configuration for Grafana. It takes about five minutes to wire up and gives you role-mapping (Authentik groups map to Grafana org roles).
  • Group-based access policies — As you add more apps, define granular access policies. Different users should only be able to reach the apps they actually need — principle of least privilege applies in homelabs too.
  • Audit logs — Authentik logs every login attempt, token issuance, and policy decision. Under Events → Logs, you can see exactly who accessed what and when. Wire these into your monitoring stack for alerting on failed login spikes.

Authentik is one of those tools that feels like overhead until the day you need it — and then you won't be able to imagine running a homelab without it. A single, auditable login surface, consistent MFA enforcement, and clean user lifecycle management is worth the operational overhead of one more Docker stack.

Enjoying this post?

Get more guides like this delivered straight to your inbox. No spam, just tech and trails.