Authentik on Docker: Self-Hosted Single Sign-On for Your Homelab

Authentik on Docker: Self-Hosted Single Sign-On for Your Homelab

If you’ve been running a homelab for any length of time, you know the authentication problem well. You’ve got Portainer over here, Nextcloud over there, Grafana on another port, maybe Gitea or Vaultwarden somewhere else — and every single one of them has its own username and password. Managing those credentials becomes a headache fast, and if you’re anything like me, you either reuse passwords (bad) or forget half of them within a week (worse).

The enterprise answer to this is Single Sign-On (SSO) — one identity provider that handles authentication for every service. And thanks to Authentik, that enterprise-grade solution is now completely available for self-hosters.

Authentik is an open-source identity provider built for exactly this use case. It supports OAuth2, OpenID Connect (OIDC), SAML, LDAP, and RADIUS — so it can authenticate against pretty much any service you’d want to run in a homelab. It’s written in Python/Django and Go, ships as a Docker image, and has a genuinely polished web UI that makes configuration much less painful than alternatives like Keycloak.

In this guide, I’ll walk you through a complete Authentik deployment on Docker, then connect it to several common homelab services so you can see exactly how the integration works.


What You’ll Need

  • A Linux host running Docker and Docker Compose (if you haven’t set that up yet, check out our complete Docker beginner’s guide)
  • A domain name or local DNS entry pointing to your server (Authentik needs a stable hostname)
  • At least 2GB of RAM — Authentik’s worker and server processes are reasonably hungry
  • A reverse proxy (Nginx Proxy Manager, Traefik, or Caddy) — we’ll cover the proxy integration

For this guide I’m using a Proxmox VM with Ubuntu 24.04 LTS, but the steps are the same on bare metal or any other VM platform.


Understanding Authentik’s Architecture

Before diving into the YAML, it helps to understand what Authentik actually runs. There are two main services:

  • authentik-server: The Django application that handles the web UI and authentication flows.
  • authentik-worker: A Celery-based worker that handles background tasks like email sending, outpost health checks, and flow executor jobs.

Both services share the same Docker image and require a PostgreSQL database and Redis instance. Redis is used as the message broker for Celery and for session caching.

There’s also the concept of an Outpost in Authentik. An outpost is a separate process (also a Docker container) that proxies requests and enforces authentication in front of services that don’t natively support OAuth2. Think of it as a sidecar auth layer — traffic hits the outpost first, gets authenticated, then gets forwarded to your actual service. This is how you can add SSO to apps that have no native OAuth2 support.


Step 1: Generate Secrets and Environment Variables

First, generate the secrets Authentik needs:

# Generate a secure secret key
python3 -c "import secrets; print(secrets.token_urlsafe(50))"

# Generate a strong PostgreSQL password
python3 -c "import secrets; print(secrets.token_hex(24))"

Save both of those outputs — you’ll use them in the next step. Never commit these to version control.

Create a working directory for your Authentik stack:

mkdir -p ~/docker/authentik/{media,certs,custom-templates}
cd ~/docker/authentik

Step 2: The Docker Compose File

Create docker-compose.yml in your ~/docker/authentik directory:

version: "3.8"

services:
  postgresql:
    image: docker.io/library/postgres:16-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB} -U ${POSTGRES_USER}"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 5s
    volumes:
      - database:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${PG_PASS:?database password required}
      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"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    volumes:
      - redis:/data

  server:
    image: ghcr.io/goauthentik/server:2025.10.0
    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"
      AUTHENTIK_EMAIL__HOST: ${EMAIL_HOST:-localhost}
      AUTHENTIK_EMAIL__PORT: ${EMAIL_PORT:-25}
      AUTHENTIK_EMAIL__FROM: ${EMAIL_FROM:-authentik@example.com}
    volumes:
      - ./media:/media
      - ./custom-templates:/templates
    ports:
      - "9000:9000"
      - "9443:9443"
    depends_on:
      - postgresql
      - redis

  worker:
    image: ghcr.io/goauthentik/server:2025.10.0
    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}
      AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
    user: root
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./media:/media
      - ./certs:/certs
      - ./custom-templates:/templates
    depends_on:
      - postgresql
      - redis

volumes:
  database:
    driver: local
  redis:
    driver: local

The worker gets access to the Docker socket because it manages outpost containers — this is optional if you’re running outposts manually or as separate Compose services. Check the Authentik releases page and replace 2025.10.0 with the latest stable version before deploying.

Now create the .env file (in the same directory):

PG_PASS=your_postgres_password_here
PG_USER=authentik
PG_DB=authentik
AUTHENTIK_SECRET_KEY=your_secret_key_here
AUTHENTIK_EMAIL__HOST=smtp.example.com
AUTHENTIK_EMAIL__PORT=587
AUTHENTIK_EMAIL__FROM=authentik@yourdomain.com

Substitute your generated values for your_postgres_password_here and your_secret_key_here.


Step 3: Start Authentik

docker compose up -d
docker compose logs -f server

On first startup, Authentik will run database migrations — this takes 30-60 seconds. Watch the logs until you see something like:

authentik.server | INFO     Starting on :9000 (HTTP) and :9443 (HTTPS)

Now navigate to http://your-server-ip:9000/if/flow/initial-setup/ to complete the initial setup. You’ll create the first admin user here — use a strong password and save it somewhere secure.


Step 4: Initial Configuration in the Admin UI

Once logged in, head to the Admin Interface (the gear icon, or navigate to /if/admin/). A few things to configure before connecting any services:

Set Your Default Domain

Go to System > Settings and set the Default domain to your server’s hostname or IP (e.g., auth.yourdomain.com). This is used in redirect URIs and email links.

Create a Test User

Navigate to Directory > Users and create a user account for yourself. Add it to the authentik Admins group if you want admin access. You’ll use this account to test SSO logins.

Review the Default Flows

Authentik ships with pre-built flows for login, logout, enrollment, and password recovery. For most homelab setups, the defaults work fine. If you want email verification or MFA, those get layered in as flow stages — but that’s an advanced topic for another day.


Step 5: Connect Nextcloud via OIDC

Let’s walk through a real integration. Nextcloud supports OpenID Connect natively through the Social Login app, so this is a good first integration to verify everything is working.

In Authentik: Create a Provider

  1. Go to Applications > ProvidersCreate
  2. Choose OAuth2/OpenID Provider
  3. Name: Nextcloud
  4. Authorization flow: default-provider-authorization-implicit-consent
  5. Client type: Confidential
  6. Copy the auto-generated Client ID and Client Secret — you’ll need these in Nextcloud
  7. Redirect URIs: https://nextcloud.yourdomain.com/apps/social_login/custom_oidc/Authentik
  8. Scopes: leave defaults (openid, email, profile)
  9. Save

In Authentik: Create an Application

  1. Go to Applications > ApplicationsCreate
  2. Name: Nextcloud, Slug: nextcloud
  3. Provider: select the one you just created
  4. Launch URL: https://nextcloud.yourdomain.com
  5. Save

In Nextcloud: Install and Configure Social Login

# Install the Social Login app
occ app:install sociallogin

# Or install it from the Nextcloud app store in the web UI

Then in Nextcloud Settings → Social Login, add a Custom OpenID Connect provider:

  • Name: Authentik
  • Title: Login with Authentik
  • Authorize URL: https://auth.yourdomain.com/application/o/nextcloud/authorize/
  • Token URL: https://auth.yourdomain.com/application/o/token/
  • User info URL: https://auth.yourdomain.com/application/o/userinfo/
  • Client ID and Secret: from the Authentik provider you created
  • Scope: openid email profile
  • User ID claim: sub

Save the settings, log out of Nextcloud, and you should now see a “Login with Authentik” button on the login page. Your Authentik user will be able to sign in directly.


Step 6: Proxy Authentication with the Authentik Outpost

Some homelab services don’t support OAuth2 at all. For these, Authentik provides a Proxy Provider and an outpost that sits in front of the service and handles authentication for it. This is incredibly useful for things like Portainer, older Grafana instances, or any service that only supports basic HTTP auth.

Create a Proxy Provider

  1. Go to Applications > ProvidersCreateProxy Provider
  2. Name: Portainer Proxy
  3. Authorization flow: default-provider-authorization-implicit-consent
  4. Mode: Forward auth (single application)
  5. External host: https://portainer.yourdomain.com
  6. Save, then create an Application pointing to this provider

Deploy the Outpost

Go to Applications > OutpostsCreate. Choose type Proxy, give it a name, and select your Portainer Proxy provider. Authentik will either manage the outpost container automatically (if the worker has Docker socket access) or give you a Docker Compose snippet to run manually.

The outpost listens on port 9000 internally. In your reverse proxy, you’ll route portainer.yourdomain.com to the outpost instead of directly to Portainer. The outpost handles the Authentik auth challenge, then forwards authenticated requests to the real Portainer container.

With Tailscale running on your homelab hosts, this setup lets you add a proper auth layer to any service without that service even needing internet exposure — the SSO flow happens over your Tailscale network.


Step 7: Enforce MFA for Extra Security

Authentik makes adding TOTP (Google Authenticator, Authy, etc.) multi-factor authentication straightforward. Navigate to Flows and Stages > Stages and you’ll find a pre-built default-authentication-totp-setup stage. To enforce it:

  1. Create a new stage of type Authenticator Validation Stage
  2. Set “Not Configured Action” to Deny (forces enrollment) or Configure (prompts first-time setup)
  3. Add this stage to the default-authentication-flow after the password stage

With MFA enforced at the Authentik level, every service behind SSO gets MFA protection — even ones that have no native MFA support of their own. That’s a significant security win for your homelab.


Step 8: LDAP Outpost for Legacy Applications

Some homelab services only speak LDAP — things like older NAS units, some monitoring tools, or custom internal applications. Authentik handles this too. Deploy an LDAP outpost and your Authentik user directory becomes an LDAP directory that any LDAP-compatible service can query.

# Test your LDAP outpost is working
ldapsearch -H ldap://your-server-ip:389 \
  -x \
  -D "cn=ldapservice,ou=serviceaccounts,dc=ldap,dc=goauthentik,dc=io" \
  -w "your-service-account-password" \
  -b "dc=ldap,dc=goauthentik,dc=io" \
  "(objectClass=user)"

Configure the bind DN to a dedicated service account user in Authentik, and you’ll see all your Authentik users returned as LDAP entries. This pairs nicely with monitoring setups where you want central auth for tools like Grafana or LibreNMS — both support LDAP login out of the box.


Troubleshooting Common Issues

Authentik is generally reliable, but a few issues come up regularly when first setting it up.

Redirect URI Mismatch

The most common OAuth2 error is a redirect URI mismatch. Authentik is strict about these — the URI your application sends must exactly match one configured in the provider. Check for trailing slashes, HTTP vs HTTPS differences, and make sure the full path is correct. The error log at Events > System Tasks will show the mismatched URI so you can copy it exactly.

Worker Not Starting

If the worker container exits immediately, it’s usually a permissions issue with the Docker socket or with the media directory. Check docker compose logs worker — if you see a socket permission error, run:

sudo chmod 666 /var/run/docker.sock

In production, it’s better to add your user to the docker group than to chmod the socket globally, but for a homelab this gets you running quickly.

Session Expiry Too Aggressive

By default Authentik sessions expire after a relatively short period. If users are getting logged out too frequently across all their SSO’d services, go to System > Settings and increase the token duration. Alternatively, configure individual providers with longer token lifetimes — this is preferable if you want different expiry windows for different applications.

Outpost Can’t Reach Authentik

Outpost containers communicate with the Authentik server using a WebSocket connection. If your outpost shows as offline in the admin UI, check that the outpost can reach the Authentik server on port 9443. On Docker networks, use the container name as the hostname — the outpost’s AUTHENTIK_HOST environment variable should point to https://server:9443 if both containers are on the same network.


Maintenance and Backups

Keep Authentik healthy with a few operational habits:

Database Backups

# Dump the PostgreSQL database
docker compose exec postgresql pg_dump -U authentik authentik > ~/backups/authentik_$(date +%Y%m%d).sql

# Restore if needed
docker compose exec -T postgresql psql -U authentik authentik < ~/backups/authentik_YYYYMMDD.sql

Updating Authentik

# Pull the new image
docker compose pull

# Restart with zero-downtime (worker first, then server)
docker compose up -d worker
docker compose up -d server

Authentik generally handles database migrations automatically on startup, but check the release notes for any version that involves major schema changes before upgrading across multiple versions at once.

Health Monitoring

Authentik exposes a health endpoint at /healthz/. If you're running Uptime Kuma, add a monitor for this endpoint — it will alert you if the server becomes unhealthy before your users do.


Wrapping Up

Authentik transforms how authentication works across a self-hosted stack. Instead of managing separate credentials for every service, you maintain a single user directory and grant access through Authentik's flow-based system. New services get SSO support in about five minutes. Users get a consistent login experience. And you get the ability to enforce MFA or disable a user account in one place and have it propagate everywhere immediately.

The initial setup investment — maybe two to three hours for a thorough deployment — pays dividends every day after that. Once you've got Nextcloud, Grafana, Portainer, and your other services all behind Authentik, you'll wonder how you ran a homelab without it.

The official Authentik documentation is genuinely excellent and covers the more advanced topics like custom branding, policy enforcement, external LDAP sources, and SAML providers. If you're running a local AI setup with Ollama or any other service that exposes a web UI, Authentik is a clean way to add a proper auth layer in front of it without modifying the service itself.

Got questions or running into issues with a specific integration? Drop a comment below — I check in on them regularly and the Authentik community on GitHub and Discord is also very active.

Enjoying this post?

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