<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Services%2FAuthentik</id>
	<title>Services/Authentik - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Services%2FAuthentik"/>
	<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Services/Authentik&amp;action=history"/>
	<updated>2026-07-06T07:35:23Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://wiki.wilsoz.com/index.php?title=Services/Authentik&amp;diff=29&amp;oldid=prev</id>
		<title>Wikibot: Genericize example password placeholder</title>
		<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Services/Authentik&amp;diff=29&amp;oldid=prev"/>
		<updated>2026-07-05T18:25:21Z</updated>

		<summary type="html">&lt;p&gt;Genericize example password placeholder&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 18:25, 5 July 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot;&gt;Line 1:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 1:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&#039;&#039;As of: 2026-07-05 — migrated from &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;`&lt;/del&gt;services/AUTHENTIK.md&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;`&lt;/del&gt;&#039;&#039;&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&#039;&#039;As of: 2026-07-05 — migrated from services/AUTHENTIK.md&#039;&#039;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;= Authentik — Identity &amp;amp; SSO Provider =&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;= Authentik — Identity &amp;amp; SSO Provider =&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l502&quot;&gt;Line 502:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 502:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;from authentik.core.models import User&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;from authentik.core.models import User&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;u = User.objects.get(username=&amp;#039;akadmin&amp;#039;)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;u = User.objects.get(username=&amp;#039;akadmin&amp;#039;)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;u.set_password(&#039;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;NewPassword123&lt;/del&gt;&#039;)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;u.set_password(&#039;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&amp;lt;choose-a-new-password-here&amp;gt;&lt;/ins&gt;&#039;)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;u.save()&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;u.save()&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;print(f&amp;#039;Reset password for {u.username}&amp;#039;)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;print(f&amp;#039;Reset password for {u.username}&amp;#039;)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;

&lt;!-- diff cache key mediawiki:diff:1.41:old-6:rev-29:php=table --&gt;
&lt;/table&gt;</summary>
		<author><name>Wikibot</name></author>
	</entry>
	<entry>
		<id>https://wiki.wilsoz.com/index.php?title=Services/Authentik&amp;diff=6&amp;oldid=prev</id>
		<title>Wikibot: Migrated from services/AUTHENTIK.md</title>
		<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Services/Authentik&amp;diff=6&amp;oldid=prev"/>
		<updated>2026-07-05T18:16:02Z</updated>

		<summary type="html">&lt;p&gt;Migrated from services/AUTHENTIK.md&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;#039;&amp;#039;As of: 2026-07-05 — migrated from `services/AUTHENTIK.md`&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
= Authentik — Identity &amp;amp; SSO Provider =&lt;br /&gt;
&lt;br /&gt;
Complete setup &amp;amp; operations guide for Authentik (identity provider, OAuth2/OIDC, LDAP directory service).&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Criticality:&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;CRITICAL — The lynchpin.&amp;#039;&amp;#039;&amp;#039; Everything on Stronghold depends on Authentik for authentication. Without it, most other services are inaccessible or in an inconsistent state. Must be recovered first in any disaster scenario.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Authentik&amp;#039;&amp;#039;&amp;#039; is a self-hosted identity provider that serves as the single source of truth for authentication across Stronghold. It provides:&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;OAuth2 / OIDC&amp;#039;&amp;#039;&amp;#039; providers for web apps (Immich, Memos, Linkwarden, Forgejo, Vaultwarden, Grafana, n8n, Matrix, Cloudflare Access)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;LDAP directory&amp;#039;&amp;#039;&amp;#039; for apps that don&amp;#039;t support OAuth (Nextcloud, Jellyfin, Stalwart mail server)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Group membership&amp;#039;&amp;#039;&amp;#039; for role-based access (authentik Admins, family-and-friends, email provisioning groups)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;User management&amp;#039;&amp;#039;&amp;#039; — all users are defined here; other services sync from Authentik&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;API tokens&amp;#039;&amp;#039;&amp;#039; for programmatic access (n8n, CI/CD, automation)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Current Users:&amp;#039;&amp;#039;&amp;#039; &lt;br /&gt;
* &amp;lt;code&amp;gt;marcus&amp;lt;/code&amp;gt; (primary admin user, member of authentik Admins)&lt;br /&gt;
* &amp;lt;code&amp;gt;akadmin&amp;lt;/code&amp;gt; (backup admin user)&lt;br /&gt;
* Service accounts: &amp;lt;code&amp;gt;stalwart-svc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;nextcloud-svc&amp;lt;/code&amp;gt; (for LDAP directory access)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Current Version:&amp;#039;&amp;#039;&amp;#039; 2025.10.3&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Access:&amp;#039;&amp;#039;&amp;#039; https://key.wilsoz.com/if/admin/&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Service Information ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Property&lt;br /&gt;
! Value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Compose file&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/authentik-app/docker-compose.yml&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Containers&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;authentik-app_server_1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;authentik-app_worker_1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;authentik-app_postgresql_1&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Database&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| PostgreSQL 16 (in-container, persisted to Docker volume &amp;lt;code&amp;gt;database&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;HTTP Port&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| 9000 (external via 9443 HTTPS via reverse proxy)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;HTTPS Port&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| 9443 (internal, for direct connections)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;LDAP Outpost Port&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| 389 (via LDAP Outpost container; internal only, reaches stronghold via &amp;lt;code&amp;gt;http://172.17.0.1:9000&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Data volumes&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/authentik-app/media&amp;lt;/code&amp;gt; (custom templates, branding)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Database volume&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;authentik-app_database&amp;lt;/code&amp;gt; (PostgreSQL data)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Networks&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;authentik-app_default&amp;lt;/code&amp;gt; (internal Docker network, MTU 1280)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Image versions&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| server/worker: &amp;lt;code&amp;gt;ghcr.io/goauthentik/server:2025.10.3&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Restart policy&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;unless-stopped&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Initial Setup from Scratch ==&lt;br /&gt;
&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
&lt;br /&gt;
* Docker and docker-compose installed&lt;br /&gt;
* Network access to PostgreSQL (internal container or external)&lt;br /&gt;
* Cloudflare DNS configured for &amp;lt;code&amp;gt;key.wilsoz.com&amp;lt;/code&amp;gt;&lt;br /&gt;
* VPS reverse proxy (nginx) configured to forward to stronghold:9000&lt;br /&gt;
&lt;br /&gt;
=== 1. Create Directory &amp;amp; Files ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
mkdir -p /library/authentik-app&lt;br /&gt;
cd /library/authentik-app&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2. Create docker-compose.yml ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;yaml&amp;quot;&amp;gt;&lt;br /&gt;
version: &amp;quot;3.8&amp;quot;&lt;br /&gt;
&lt;br /&gt;
services:&lt;br /&gt;
  postgresql:&lt;br /&gt;
    image: docker.io/library/postgres:16-alpine&lt;br /&gt;
    restart: unless-stopped&lt;br /&gt;
    healthcheck:&lt;br /&gt;
      test: [&amp;quot;CMD-SHELL&amp;quot;, &amp;quot;pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}&amp;quot;]&lt;br /&gt;
      interval: 30s&lt;br /&gt;
      timeout: 5s&lt;br /&gt;
      retries: 5&lt;br /&gt;
      start_period: 20s&lt;br /&gt;
    volumes:&lt;br /&gt;
      - database:/var/lib/postgresql/data&lt;br /&gt;
    env_file:&lt;br /&gt;
      - .env&lt;br /&gt;
    environment:&lt;br /&gt;
      POSTGRES_DB: ${PG_DB:-authentik}&lt;br /&gt;
      POSTGRES_USER: ${PG_USER:-authentik}&lt;br /&gt;
      POSTGRES_PASSWORD: ${PG_PASS:?database password required}&lt;br /&gt;
&lt;br /&gt;
  server:&lt;br /&gt;
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.3}&lt;br /&gt;
    restart: unless-stopped&lt;br /&gt;
    command: server&lt;br /&gt;
    ports:&lt;br /&gt;
      - &amp;quot;${COMPOSE_PORT_HTTP:-9000}:9000&amp;quot;&lt;br /&gt;
      - &amp;quot;${COMPOSE_PORT_HTTPS:-9443}:9443&amp;quot;&lt;br /&gt;
    env_file:&lt;br /&gt;
      - .env&lt;br /&gt;
    environment:&lt;br /&gt;
      AUTHENTIK_POSTGRESQL__HOST: postgresql&lt;br /&gt;
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}&lt;br /&gt;
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}&lt;br /&gt;
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}&lt;br /&gt;
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}&lt;br /&gt;
    volumes:&lt;br /&gt;
      - ./media:/media&lt;br /&gt;
      - ./custom-templates:/templates&lt;br /&gt;
    depends_on:&lt;br /&gt;
      postgresql:&lt;br /&gt;
        condition: service_healthy&lt;br /&gt;
&lt;br /&gt;
  worker:&lt;br /&gt;
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.3}&lt;br /&gt;
    restart: unless-stopped&lt;br /&gt;
    command: worker&lt;br /&gt;
    user: root&lt;br /&gt;
    env_file:&lt;br /&gt;
      - .env&lt;br /&gt;
    environment:&lt;br /&gt;
      AUTHENTIK_POSTGRESQL__HOST: postgresql&lt;br /&gt;
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}&lt;br /&gt;
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}&lt;br /&gt;
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}&lt;br /&gt;
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}&lt;br /&gt;
    volumes:&lt;br /&gt;
      - /var/run/docker.sock:/var/run/docker.sock&lt;br /&gt;
      - ./media:/media&lt;br /&gt;
      - ./certs:/certs&lt;br /&gt;
      - ./custom-templates:/templates&lt;br /&gt;
    depends_on:&lt;br /&gt;
      postgresql:&lt;br /&gt;
        condition: service_healthy&lt;br /&gt;
&lt;br /&gt;
volumes:&lt;br /&gt;
  database:&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3. Create .env File ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# PostgreSQL&lt;br /&gt;
PG_DB=authentik&lt;br /&gt;
PG_USER=authentik&lt;br /&gt;
PG_PASS=&amp;lt;generate-strong-password-32-chars&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Authentik&lt;br /&gt;
AUTHENTIK_SECRET_KEY=&amp;lt;generate-strong-secret-key-50-chars&amp;gt;&lt;br /&gt;
AUTHENTIK_IMAGE=ghcr.io/goauthentik/server&lt;br /&gt;
AUTHENTIK_TAG=2025.10.3&lt;br /&gt;
&lt;br /&gt;
# Ports&lt;br /&gt;
COMPOSE_PORT_HTTP=9000&lt;br /&gt;
COMPOSE_PORT_HTTPS=9443&lt;br /&gt;
&lt;br /&gt;
# Bootstrap (initial admin)&lt;br /&gt;
AUTHENTIK_BOOTSTRAP_PASSWORD=&amp;lt;temporary-init-password&amp;gt;&lt;br /&gt;
AUTHENTIK_BOOTSTRAP_TOKEN=&amp;lt;temporary-init-token&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Important:&amp;#039;&amp;#039;&amp;#039; Generate secure random values for passwords and secrets:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl rand -base64 32  # For PG_PASS and AUTHENTIK_SECRET_KEY&lt;br /&gt;
openssl rand -hex 16     # For AUTHENTIK_BOOTSTRAP_TOKEN&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4. Start Authentik ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /library/authentik-app&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wait for PostgreSQL to be healthy and server to start (~30-60 seconds):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker compose logs -f server&lt;br /&gt;
# Watch for &amp;quot;Application startup complete&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5. Initial Admin User ===&lt;br /&gt;
&lt;br /&gt;
After startup, navigate to &amp;lt;code&amp;gt;https://key.wilsoz.com/if/admin/&amp;lt;/code&amp;gt;:&lt;br /&gt;
* Username: &amp;lt;code&amp;gt;akadmin&amp;lt;/code&amp;gt;&lt;br /&gt;
* Password: value of &amp;lt;code&amp;gt;AUTHENTIK_BOOTSTRAP_PASSWORD&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;First steps in Admin UI:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Go to &amp;#039;&amp;#039;&amp;#039;Directory → Users → Create&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** Username: &amp;lt;code&amp;gt;marcus&amp;lt;/code&amp;gt;&lt;br /&gt;
** Email: &amp;lt;code&amp;gt;marcus@wilsoz.com&amp;lt;/code&amp;gt;&lt;br /&gt;
** Set password (or let user set on first login)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; Groups: Add to &amp;#039;&amp;#039;&amp;#039;authentik Admins**&lt;br /&gt;
&lt;br /&gt;
# Go to &amp;#039;&amp;#039;&amp;#039;Directory → Groups → Create&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;authentik Admins** (if not already created)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;family-and-friends** (for app access)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;craniumslows-email** (for email provisioning)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;wilsoz-email** (for email provisioning)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;ldap-search** (for LDAP bind accounts)&lt;br /&gt;
&lt;br /&gt;
# Add service users:&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;Directory → Users → Create**&lt;br /&gt;
** Username: &amp;lt;code&amp;gt;stalwart-svc&amp;lt;/code&amp;gt;, Email: &amp;lt;code&amp;gt;stalwart-svc@internal&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; Groups: Add to &amp;#039;&amp;#039;&amp;#039;ldap-search**&lt;br /&gt;
** Username: &amp;lt;code&amp;gt;nextcloud-svc&amp;lt;/code&amp;gt;, Email: &amp;lt;code&amp;gt;nextcloud-svc@internal&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; Groups: Add to &amp;#039;&amp;#039;&amp;#039;ldap-search**&lt;br /&gt;
&lt;br /&gt;
=== 6. Set Up LDAP Outpost ===&lt;br /&gt;
&lt;br /&gt;
LDAP is required for Stalwart (mail server), Nextcloud (CalDAV/CardDAV), and Jellyfin (media server) to authenticate.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;In Authentik Admin UI:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Go to &amp;#039;&amp;#039;&amp;#039;Infrastructure → Outposts → Create&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** Name: &amp;lt;code&amp;gt;authentik-ldap&amp;lt;/code&amp;gt;&lt;br /&gt;
** Type: LDAP&lt;br /&gt;
# In &amp;#039;&amp;#039;&amp;#039;Directory → LDAP → Configure LDAP&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** Base DN: &amp;lt;code&amp;gt;dc=ldap,dc=goauthentik,dc=io&amp;lt;/code&amp;gt;&lt;br /&gt;
** User base: &amp;lt;code&amp;gt;ou=users,dc=ldap,dc=goauthentik,dc=io&amp;lt;/code&amp;gt;&lt;br /&gt;
# Assign the LDAP outpost to the LDAP provider&lt;br /&gt;
&lt;br /&gt;
The LDAP server will be reachable at &amp;lt;code&amp;gt;172.17.0.1:389&amp;lt;/code&amp;gt; from other containers.&lt;br /&gt;
&lt;br /&gt;
=== 7. Configure OAuth2 Providers ===&lt;br /&gt;
&lt;br /&gt;
For each OAuth application (Immich, Memos, Linkwarden, etc.), create an OAuth2 provider in Authentik:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Immich example:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Go to &amp;#039;&amp;#039;&amp;#039;Applications → Providers → Create OAuth2/OpenID Provider&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** Name: &amp;lt;code&amp;gt;immich-oauth&amp;lt;/code&amp;gt;&lt;br /&gt;
** Authorization flow: &amp;lt;code&amp;gt;default-authentication-flow&amp;lt;/code&amp;gt;&lt;br /&gt;
** Client type: &amp;lt;code&amp;gt;Confidential&amp;lt;/code&amp;gt;&lt;br /&gt;
** Redirect URIs:&lt;br /&gt;
     ```&lt;br /&gt;
     https://photos.wilsoz.com/auth/login/oauth2/callback&lt;br /&gt;
     https://photos.wilsoz.com/auth/login/oauth2/mobile-redirect&lt;br /&gt;
     ```&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Save&amp;#039;&amp;#039;&amp;#039; — copy the generated Client ID and Client Secret&lt;br /&gt;
# Add property mappings:&lt;br /&gt;
** &amp;lt;code&amp;gt;authentik default OAuth Mapping: OpenID &amp;#039;openid&amp;#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;authentik default OAuth Mapping: OpenID &amp;#039;email&amp;#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
** &amp;lt;code&amp;gt;authentik default OAuth Mapping: OpenID &amp;#039;profile&amp;#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
# Enable: &amp;lt;code&amp;gt;include_claims_in_id_token&amp;lt;/code&amp;gt;&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Assign a signing key&amp;#039;&amp;#039;&amp;#039; (see Critical Gotchas section below)&lt;br /&gt;
&lt;br /&gt;
Repeat for each app.&lt;br /&gt;
&lt;br /&gt;
=== 8. Configure VPS Reverse Proxy ===&lt;br /&gt;
&lt;br /&gt;
On VPS, add nginx server block:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
server {&lt;br /&gt;
    listen 443 ssl http2;&lt;br /&gt;
    server_name key.wilsoz.com;&lt;br /&gt;
&lt;br /&gt;
    ssl_certificate /etc/letsencrypt/live/key.wilsoz.com/fullchain.pem;&lt;br /&gt;
    ssl_certificate_key /etc/letsencrypt/live/key.wilsoz.com/privkey.pem;&lt;br /&gt;
&lt;br /&gt;
    location / {&lt;br /&gt;
        proxy_pass http://100.64.207.155:9000;&lt;br /&gt;
        proxy_set_header Host $host;&lt;br /&gt;
        proxy_set_header X-Real-IP $remote_addr;&lt;br /&gt;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;br /&gt;
        proxy_set_header X-Forwarded-Proto $scheme;&lt;br /&gt;
        proxy_http_version 1.1;&lt;br /&gt;
        proxy_set_header Upgrade $http_upgrade;&lt;br /&gt;
        proxy_set_header Connection &amp;quot;upgrade&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reload nginx:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo systemctl reload nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Disaster Mode A: Configuration Corruption ==&lt;br /&gt;
&lt;br /&gt;
=== Symptoms ===&lt;br /&gt;
&lt;br /&gt;
* Can&amp;#039;t log into admin interface&lt;br /&gt;
* OAuth providers missing or broken&lt;br /&gt;
* LDAP directory returning errors&lt;br /&gt;
* Groups or users deleted accidentally&lt;br /&gt;
* Provider credentials lost&lt;br /&gt;
&lt;br /&gt;
=== Recovery Steps ===&lt;br /&gt;
&lt;br /&gt;
⚠️ &amp;#039;&amp;#039;&amp;#039;This mode resets Authentik to a fresh state. User data will be lost unless you have a PostgreSQL backup (see Disaster Mode B).&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
==== Option 1: Full Reset (if Disaster Mode B not available) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /library/authentik-app&lt;br /&gt;
&lt;br /&gt;
# Stop all containers&lt;br /&gt;
docker compose down&lt;br /&gt;
&lt;br /&gt;
# Remove the database volume (this deletes all Authentik data)&lt;br /&gt;
docker volume rm authentik-app_database&lt;br /&gt;
&lt;br /&gt;
# Start from scratch&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# Wait for startup&lt;br /&gt;
sleep 30&lt;br /&gt;
docker logs server | grep &amp;quot;Application startup complete&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Follow &amp;quot;Initial Setup from Scratch&amp;quot; section above&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Option 2: Selective Reset (if you only need to fix one app) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Access the Django shell&lt;br /&gt;
docker exec authentik-app_server_1 ak shell&lt;br /&gt;
&lt;br /&gt;
# Example: Fix an OAuth provider&amp;#039;s signing key&lt;br /&gt;
from authentik.providers.oauth2.models import OAuth2Provider&lt;br /&gt;
from authentik.crypto.models import CertificateKeyPair&lt;br /&gt;
&lt;br /&gt;
p = OAuth2Provider.objects.get(name=&amp;#039;immich-oauth&amp;#039;)&lt;br /&gt;
jwt_key = CertificateKeyPair.objects.get(name=&amp;#039;authentik Internal JWT Certificate&amp;#039;)&lt;br /&gt;
p.signing_key = jwt_key&lt;br /&gt;
p.save()&lt;br /&gt;
print(f&amp;#039;Updated {p.name} with JWT signing key&amp;#039;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Disaster Mode B: Drive Failure / Full Recovery ==&lt;br /&gt;
&lt;br /&gt;
=== If Backups Accessible ===&lt;br /&gt;
&lt;br /&gt;
Authentik data is backed up in two ways:&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;PostgreSQL dump&amp;#039;&amp;#039;&amp;#039; at Hetzner (in &amp;lt;code&amp;gt;/dumps/authentik-YYYYMMDD.sql.gz&amp;lt;/code&amp;gt;)&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Media files&amp;#039;&amp;#039;&amp;#039; at Hetzner (in &amp;lt;code&amp;gt;/data/authentik/&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==== Recovery procedure: ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# 1. Set up fresh Authentik (follow &amp;quot;Initial Setup from Scratch&amp;quot; through step 4)&lt;br /&gt;
cd /library/authentik-app&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# 2. Wait for PostgreSQL to be ready&lt;br /&gt;
sleep 30&lt;br /&gt;
&lt;br /&gt;
# 3. Drop the initial empty database&lt;br /&gt;
docker exec authentik-app_postgresql_1 psql -U authentik -d postgres -c &amp;quot;DROP DATABASE authentik;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# 4. Create a fresh empty database&lt;br /&gt;
docker exec authentik-app_postgresql_1 psql -U authentik -d postgres -c &amp;quot;CREATE DATABASE authentik;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# 5. Restore from backup dump (substitute actual date)&lt;br /&gt;
# First, extract the backup from Hetzner:&lt;br /&gt;
# (You&amp;#039;ll need ssh-key access to u540853.your-storagebox.de port 23)&lt;br /&gt;
ssh -p 23 u540853@u540853.your-storagebox.de &amp;quot;cat backups/authentik-20260512.sql.gz&amp;quot; | \&lt;br /&gt;
  gunzip | docker exec -i authentik-app_postgresql_1 psql -U authentik -d authentik&lt;br /&gt;
&lt;br /&gt;
# 6. Restore media files&lt;br /&gt;
rsync -av -e &amp;quot;ssh -p 23&amp;quot; u540853@u540853.your-storagebox.de:backups/data/authentik/ /library/authentik-app/media/&lt;br /&gt;
&lt;br /&gt;
# 7. Restart Authentik to pick up restored data&lt;br /&gt;
docker compose restart server worker&lt;br /&gt;
&lt;br /&gt;
# 8. Verify&lt;br /&gt;
docker logs server | grep &amp;quot;Application startup complete&amp;quot;&lt;br /&gt;
curl -s https://key.wilsoz.com/if/admin/ | grep -q &amp;quot;authentik&amp;quot; &amp;amp;&amp;amp; echo &amp;quot;Admin UI responding&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== If Backups NOT Accessible ===&lt;br /&gt;
&lt;br /&gt;
Fall back to Mode A (full reset). Note: &amp;#039;&amp;#039;&amp;#039;All user data, OAuth provider credentials, and LDAP settings will be lost.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
After reset, you&amp;#039;ll need to:&lt;br /&gt;
# Recreate all users and groups&lt;br /&gt;
# Recreate all OAuth2 providers and get new Client IDs/Secrets&lt;br /&gt;
# Update all dependent services (.env files) with new credentials&lt;br /&gt;
# Recreate LDAP configuration and outpost&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Data Locations ==&lt;br /&gt;
&lt;br /&gt;
=== Authentik Data on Host ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
/library/authentik-app/&lt;br /&gt;
├── docker-compose.yml       (service definition)&lt;br /&gt;
├── .env                     (secrets: PG_PASS, AUTHENTIK_SECRET_KEY)&lt;br /&gt;
├── media/                   (custom templates, branding, uploads)&lt;br /&gt;
└── custom-templates/        (HTML templates for login pages, etc.)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PostgreSQL Database Volume ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
authentik-app_database (Docker volume)&lt;br /&gt;
├── postgresql/              (database files)&lt;br /&gt;
└── ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Contains:&lt;br /&gt;
* All users, groups, group memberships&lt;br /&gt;
* All OAuth2 providers, applications, credentials&lt;br /&gt;
* All LDAP configuration&lt;br /&gt;
* All API tokens&lt;br /&gt;
* All authenticator devices (TOTP, WebAuthn)&lt;br /&gt;
* All event logs&lt;br /&gt;
&lt;br /&gt;
=== Backup Locations ===&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;PostgreSQL dump:&amp;#039;&amp;#039;&amp;#039; Hetzner &amp;lt;code&amp;gt;/dumps/authentik-YYYYMMDD.sql.gz&amp;lt;/code&amp;gt; (daily at 03:00 CST)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Media files:&amp;#039;&amp;#039;&amp;#039; Hetzner &amp;lt;code&amp;gt;/data/authentik/&amp;lt;/code&amp;gt; (daily Restic snapshot)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Filesystem snapshot:&amp;#039;&amp;#039;&amp;#039; Timeshift snapshot on &amp;lt;code&amp;gt;/dev/md1&amp;lt;/code&amp;gt; (weekly)&lt;br /&gt;
&lt;br /&gt;
=== What&amp;#039;s NOT Backed Up ===&lt;br /&gt;
&lt;br /&gt;
* Container images (re-downloadable from Docker Hub)&lt;br /&gt;
* node_modules and build artifacts (not present in Authentik)&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Critical Gotchas ==&lt;br /&gt;
&lt;br /&gt;
=== 1. OAuth2 Signing Key Assignment — &amp;#039;&amp;#039;&amp;#039;MANDATORY&amp;#039;&amp;#039;&amp;#039; ===&lt;br /&gt;
&lt;br /&gt;
⚠️ &amp;#039;&amp;#039;&amp;#039;If you don&amp;#039;t assign a signing key to an OAuth2 provider, the JWKS endpoint returns an empty object &amp;lt;code&amp;gt;{}&amp;lt;/code&amp;gt;, and all clients trying to validate tokens will fail.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Every OAuth2 provider MUST have:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* A signing key assigned: Use &amp;#039;&amp;#039;&amp;#039;&amp;lt;code&amp;gt;authentik Internal JWT Certificate&amp;lt;/code&amp;gt;&amp;#039;&amp;#039;&amp;#039; (HS256 algorithm)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;NOT&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;authentik Self-signed Certificate&amp;lt;/code&amp;gt; (RS256) — some apps like Mealie reject RS256&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Assignment code:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec authentik-app_server_1 ak shell -c &amp;quot;&lt;br /&gt;
from authentik.providers.oauth2.models import OAuth2Provider&lt;br /&gt;
from authentik.crypto.models import CertificateKeyPair&lt;br /&gt;
&lt;br /&gt;
p = OAuth2Provider.objects.get(name=&amp;#039;immich-oauth&amp;#039;)&lt;br /&gt;
jwt_key = CertificateKeyPair.objects.get(name=&amp;#039;authentik Internal JWT Certificate&amp;#039;)&lt;br /&gt;
p.signing_key = jwt_key&lt;br /&gt;
p.save()&lt;br /&gt;
print(f&amp;#039;Assigned signing key to {p.name}&amp;#039;)&lt;br /&gt;
&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2. Property Mappings for OAuth Scopes ===&lt;br /&gt;
&lt;br /&gt;
OAuth providers need explicit property mappings for scopes like &amp;lt;code&amp;gt;email&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;profile&amp;lt;/code&amp;gt;. Without them:&lt;br /&gt;
* OIDC discovery endpoint returns only &amp;lt;code&amp;gt;[&amp;quot;openid&amp;quot;]&amp;lt;/code&amp;gt;&lt;br /&gt;
* Apps can&amp;#039;t get user email or name from token&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Always add these three mappings to every OAuth provider:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;lt;code&amp;gt;authentik default OAuth Mapping: OpenID &amp;#039;openid&amp;#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;authentik default OAuth Mapping: OpenID &amp;#039;email&amp;#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;authentik default OAuth Mapping: OpenID &amp;#039;profile&amp;#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Also enable: &amp;lt;code&amp;gt;include_claims_in_id_token = True&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3. LDAP Outpost Internal-Only ===&lt;br /&gt;
&lt;br /&gt;
The LDAP outpost (port 389) is &amp;#039;&amp;#039;&amp;#039;internal to Docker networking only&amp;#039;&amp;#039;&amp;#039;. External apps can&amp;#039;t reach it directly. Instead:&lt;br /&gt;
* Internal Docker containers reach it at &amp;lt;code&amp;gt;172.17.0.1:389&amp;lt;/code&amp;gt;&lt;br /&gt;
* Other hosts on LAN reach it via Stalwart LDAP passthrough (Stalwart exposes LDAP to port 389 externally)&lt;br /&gt;
&lt;br /&gt;
=== 4. Bootstrap Credentials Not Persisted ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AUTHENTIK_BOOTSTRAP_PASSWORD&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AUTHENTIK_BOOTSTRAP_TOKEN&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; are only used on first startup. After that, they&amp;#039;re ignored. If you lose them:&lt;br /&gt;
* You still have the &amp;lt;code&amp;gt;akadmin&amp;lt;/code&amp;gt; user created from bootstrap&lt;br /&gt;
* If you lose the akadmin password, reset via Django shell:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec authentik-app_server_1 ak shell -c &amp;quot;&lt;br /&gt;
from authentik.core.models import User&lt;br /&gt;
u = User.objects.get(username=&amp;#039;akadmin&amp;#039;)&lt;br /&gt;
u.set_password(&amp;#039;NewPassword123&amp;#039;)&lt;br /&gt;
u.save()&lt;br /&gt;
print(f&amp;#039;Reset password for {u.username}&amp;#039;)&lt;br /&gt;
&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5. Secret Key Rotation (Rare but Important) ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AUTHENTIK_SECRET_KEY&amp;lt;/code&amp;gt; is used to encrypt stored secrets (OAuth credentials, etc.). &amp;#039;&amp;#039;&amp;#039;If you change this .env value, all encrypted secrets become unreadable.&amp;#039;&amp;#039;&amp;#039; Only change if:&lt;br /&gt;
* You&amp;#039;re migrating to a new server&lt;br /&gt;
* You suspect key compromise&lt;br /&gt;
&lt;br /&gt;
When rotating:&lt;br /&gt;
# Export all OAuth providers&amp;#039; client secrets before changing&lt;br /&gt;
# Update AUTHENTIK_SECRET_KEY&lt;br /&gt;
# Restart (secrets will need to be re-entered)&lt;br /&gt;
&lt;br /&gt;
=== 6. Group Membership Propagation Delay ===&lt;br /&gt;
&lt;br /&gt;
Changes to group membership (e.g., adding a user to a group) are propagated asynchronously via the worker. If an app isn&amp;#039;t seeing the new group:&lt;br /&gt;
* Wait 5-10 seconds&lt;br /&gt;
* Restart the worker: &amp;lt;code&amp;gt;docker compose restart worker&amp;lt;/code&amp;gt;&lt;br /&gt;
* Check the worker logs: &amp;lt;code&amp;gt;docker logs worker | tail -20&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 7. API Token Expiry ===&lt;br /&gt;
&lt;br /&gt;
API tokens can have an expiry. The current claude-api-token (created 2026-02-16) is set to &amp;#039;&amp;#039;&amp;#039;never expire&amp;#039;&amp;#039;&amp;#039;. Check token settings:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec authentik-app_server_1 ak shell -c &amp;quot;&lt;br /&gt;
from authentik.core.models import Token&lt;br /&gt;
t = Token.objects.get(identifier=&amp;#039;claude-api-token&amp;#039;)&lt;br /&gt;
print(f&amp;#039;Token: {t.identifier}&amp;#039;)&lt;br /&gt;
print(f&amp;#039;Expires: {t.expires}&amp;#039;)&lt;br /&gt;
&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a token expires, create a new one in the Admin UI (User → Tokens) and update all scripts/services using it.&lt;br /&gt;
&lt;br /&gt;
=== 8. LDAP Bind Accounts Must Be in ldap-search Group ===&lt;br /&gt;
&lt;br /&gt;
Any user/service that needs to query the LDAP directory must be a member of the &amp;#039;&amp;#039;&amp;#039;&amp;lt;code&amp;gt;ldap-search&amp;lt;/code&amp;gt;&amp;#039;&amp;#039;&amp;#039; group. Currently:&lt;br /&gt;
* &amp;lt;code&amp;gt;stalwart-svc&amp;lt;/code&amp;gt; — for mail server LDAP lookups&lt;br /&gt;
* &amp;lt;code&amp;gt;nextcloud-svc&amp;lt;/code&amp;gt; — for CalDAV/CardDAV user sync&lt;br /&gt;
&lt;br /&gt;
Without group membership, LDAP bind fails with &amp;quot;Access Denied&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Verification Checklist ==&lt;br /&gt;
&lt;br /&gt;
After recovery, verify:&lt;br /&gt;
&lt;br /&gt;
* [ ] &amp;#039;&amp;#039;&amp;#039;Admin UI accessible:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;https://key.wilsoz.com/if/admin/&amp;lt;/code&amp;gt; → login as marcus&lt;br /&gt;
* [ ] &amp;#039;&amp;#039;&amp;#039;OAuth discovery endpoint working:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  ```bash&lt;br /&gt;
  curl -s https://key.wilsoz.com/application/o/immich/.well-known/openid-configuration | jq &amp;#039;.scopes_supported&amp;#039;&lt;br /&gt;
  # Should include: [&amp;quot;openid&amp;quot;, &amp;quot;email&amp;quot;, &amp;quot;profile&amp;quot;]&lt;br /&gt;
  ```&lt;br /&gt;
* [ ] &amp;#039;&amp;#039;&amp;#039;JWKS endpoint has keys:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  ```bash&lt;br /&gt;
  curl -s https://key.wilsoz.com/application/o/immich/jwks/ | jq &amp;#039;.keys | length&amp;#039;&lt;br /&gt;
  # Should return a number &amp;gt; 0&lt;br /&gt;
  ```&lt;br /&gt;
* [ ] &amp;#039;&amp;#039;&amp;#039;LDAP directory responding:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  ```bash&lt;br /&gt;
  ldapsearch -H ldap://172.17.0.1:389 -D &amp;quot;cn=stalwart-svc,ou=users,dc=ldap,dc=goauthentik,dc=io&amp;quot; \&lt;br /&gt;
    -w &amp;lt;stalwart-svc-password&amp;gt; -b &amp;quot;ou=users,dc=ldap,dc=goauthentik,dc=io&amp;quot; &amp;quot;uid=*&amp;quot; | grep &amp;quot;numResponses&amp;quot;&lt;br /&gt;
  # Should return matches (or at least show successful bind)&lt;br /&gt;
  ```&lt;br /&gt;
* [ ] &amp;#039;&amp;#039;&amp;#039;Users present:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  ```bash&lt;br /&gt;
  docker exec authentik-app_server_1 ak shell -c &amp;quot;from authentik.core.models import User; print(&amp;#039;\n&amp;#039;.join([u.username for u in User.objects.all()]))&amp;quot;&lt;br /&gt;
  # Should show: akadmin, marcus, stalwart-svc, nextcloud-svc&lt;br /&gt;
  ```&lt;br /&gt;
* [ ] &amp;#039;&amp;#039;&amp;#039;Groups present:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  ```bash&lt;br /&gt;
  docker exec authentik-app_server_1 ak shell -c &amp;quot;from authentik.core.models import Group; print(&amp;#039;\n&amp;#039;.join([g.name for g in Group.objects.all()]))&amp;quot;&lt;br /&gt;
  # Should show all configured groups&lt;br /&gt;
  ```&lt;br /&gt;
* [ ] &amp;#039;&amp;#039;&amp;#039;OAuth providers have signing keys:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  ```bash&lt;br /&gt;
  docker exec authentik-app_server_1 ak shell -c &amp;quot;&lt;br /&gt;
  from authentik.providers.oauth2.models import OAuth2Provider&lt;br /&gt;
  for p in OAuth2Provider.objects.all():&lt;br /&gt;
    has_key = p.signing_key is not None&lt;br /&gt;
    print(f&amp;#039;{p.name}: signing_key={has_key}&amp;#039;)&lt;br /&gt;
  &amp;quot;&lt;br /&gt;
  # All should show: signing_key=True&lt;br /&gt;
  ```&lt;br /&gt;
* [ ] &amp;#039;&amp;#039;&amp;#039;Dependent services can authenticate:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** [ ] Immich: Try OAuth login at https://photos.wilsoz.com&lt;br /&gt;
** [ ] Linkwarden: Try OAuth login at https://bookmarks.wilsoz.com&lt;br /&gt;
** [ ] Memos: Try OAuth login at https://notes.wilsoz.com&lt;br /&gt;
** [ ] Stalwart IMAP: Test LDAP lookup for mail routing&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Dependencies ==&lt;br /&gt;
&lt;br /&gt;
=== What Authentik Depends On ===&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;PostgreSQL 16&amp;#039;&amp;#039;&amp;#039; — must be healthy before server/worker start&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Docker&amp;#039;&amp;#039;&amp;#039; — for container runtime&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Network access to Cloudflare DNS&amp;#039;&amp;#039;&amp;#039; — for DNS resolution of &amp;lt;code&amp;gt;key.wilsoz.com&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;VPS reverse proxy&amp;#039;&amp;#039;&amp;#039; — nginx on VPS must forward &amp;lt;code&amp;gt;key.wilsoz.com:443&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;stronghold:9000&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== What Depends On Authentik ===&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;All OAuth2 consumers:&amp;#039;&amp;#039;&amp;#039; Immich, Memos, Linkwarden, Forgejo, Vaultwarden, Grafana, n8n, Matrix&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;All LDAP consumers:&amp;#039;&amp;#039;&amp;#039; Nextcloud, Jellyfin, Stalwart (mail server)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Cloudflare Access:&amp;#039;&amp;#039;&amp;#039; For gating n8n form URLs&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Email provisioning workflow (n8n):&amp;#039;&amp;#039;&amp;#039; Watches Authentik groups for email user provisioning&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Nginx reverse proxy:&amp;#039;&amp;#039;&amp;#039; Serves key.wilsoz.com → Authentik&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Operations ==&lt;br /&gt;
&lt;br /&gt;
=== Start/Stop/Restart ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /library/authentik-app&lt;br /&gt;
&lt;br /&gt;
# Start&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# Stop&lt;br /&gt;
docker compose down&lt;br /&gt;
&lt;br /&gt;
# Restart&lt;br /&gt;
docker compose restart server worker&lt;br /&gt;
&lt;br /&gt;
# Full restart (clears caches)&lt;br /&gt;
docker compose down &amp;amp;&amp;amp; sleep 2 &amp;amp;&amp;amp; docker compose up -d&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== View Logs ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Server logs&lt;br /&gt;
docker logs -f server&lt;br /&gt;
&lt;br /&gt;
# Worker logs (for background tasks)&lt;br /&gt;
docker logs -f worker&lt;br /&gt;
&lt;br /&gt;
# PostgreSQL logs (for DB issues)&lt;br /&gt;
docker logs postgresql&lt;br /&gt;
&lt;br /&gt;
# All together&lt;br /&gt;
docker compose logs -f&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Access Django Shell (Advanced) ===&lt;br /&gt;
&lt;br /&gt;
For one-off admin tasks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec authentik-app_server_1 ak shell&lt;br /&gt;
&lt;br /&gt;
# Example: List all users&lt;br /&gt;
from authentik.core.models import User&lt;br /&gt;
for u in User.objects.all():&lt;br /&gt;
    print(f&amp;quot;{u.username}: {u.email}, groups: {list(u.groups.values_list(&amp;#039;name&amp;#039;, flat=True))}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Example: Create API token&lt;br /&gt;
from authentik.core.models import Token&lt;br /&gt;
t = Token.objects.create(user=User.objects.get(username=&amp;#039;marcus&amp;#039;), identifier=&amp;#039;my-api-key&amp;#039;)&lt;br /&gt;
print(f&amp;quot;Token: {t.key}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Exit: Ctrl+D&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Backup PostgreSQL Manually ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Full database dump&lt;br /&gt;
docker exec authentik-app_postgresql_1 pg_dump -U authentik authentik | \&lt;br /&gt;
  gzip &amp;gt; /home/mnw/backups/authentik-manual-$(date +%Y%m%d-%H%M%S).sql.gz&lt;br /&gt;
&lt;br /&gt;
# Or via Restic (for Hetzner backup)&lt;br /&gt;
cd /library/backup-stack &amp;amp;&amp;amp; docker compose exec restic-backup bash -c &amp;quot;&lt;br /&gt;
  export RESTIC_REPOSITORY=\$HETZNER_REPO&lt;br /&gt;
  export RESTIC_PASSWORD=\$HETZNER_PASSWORD&lt;br /&gt;
  # Make dump available in /data&lt;br /&gt;
  docker exec authentik-app_postgresql_1 pg_dump -U authentik authentik | gzip &amp;gt; /data/dumps/authentik-manual.sql.gz&lt;br /&gt;
  restic backup /data/dumps/&lt;br /&gt;
&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Can&amp;#039;t log into admin UI ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Symptoms:&amp;#039;&amp;#039;&amp;#039; 403 Forbidden, or password rejected&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Fixes:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Check if server is running: &amp;lt;code&amp;gt;docker ps | grep server&amp;lt;/code&amp;gt;&lt;br /&gt;
# Reset akadmin password via Django shell (see Operations section)&lt;br /&gt;
# Check logs: &amp;lt;code&amp;gt;docker logs server | grep -i &amp;quot;error\|auth&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== OAuth login fails with &amp;quot;invalid_grant&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Symptoms:&amp;#039;&amp;#039;&amp;#039; Client authentication failed, or code exchange error&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Causes &amp;amp; fixes:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Client secret mismatch: Verify in Authentik provider matches app&amp;#039;s .env&lt;br /&gt;
* Provider deleted: Recreate OAuth provider and update app config&lt;br /&gt;
* Redirect URI mismatch: Ensure app&amp;#039;s callback URL matches provider&amp;#039;s redirect URIs exactly&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Debug:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker logs server | grep -i &amp;quot;oauth\|invalid_grant&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== LDAP bind fails ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Symptoms:&amp;#039;&amp;#039;&amp;#039; Stalwart/Nextcloud can&amp;#039;t authenticate users&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Fixes:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Verify LDAP outpost is running: &amp;lt;code&amp;gt;docker ps | grep outposts&amp;lt;/code&amp;gt;&lt;br /&gt;
# Check user is in &amp;lt;code&amp;gt;ldap-search&amp;lt;/code&amp;gt; group: Admin UI → Directory → Groups → ldap-search&lt;br /&gt;
# Test bind from stronghold:&lt;br /&gt;
   ```bash&lt;br /&gt;
   ldapsearch -H ldap://172.17.0.1:389 -D &amp;quot;cn=stalwart-svc,ou=users,dc=ldap,dc=goauthentik,dc=io&amp;quot; \&lt;br /&gt;
     -w &amp;lt;password&amp;gt; -b &amp;quot;ou=users&amp;quot; &amp;quot;uid=marcus&amp;quot;&lt;br /&gt;
   ```&lt;br /&gt;
&lt;br /&gt;
=== Worker not picking up changes ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Symptoms:&amp;#039;&amp;#039;&amp;#039; Group membership not reflected, or other background tasks stuck&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Fix:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker compose restart worker&lt;br /&gt;
docker logs worker --tail=50&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Database corruption ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Symptoms:&amp;#039;&amp;#039;&amp;#039; PostgreSQL won&amp;#039;t start, or &amp;quot;ERROR: relation does not exist&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Fix:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Try recovery from backup (Disaster Mode B)&lt;br /&gt;
* If no backup: full reset (Disaster Mode A) — all data lost&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Links &amp;amp; References ==&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Authentik Docs:&amp;#039;&amp;#039;&amp;#039; https://goauthentik.io/docs/&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Authentik Admin UI:&amp;#039;&amp;#039;&amp;#039; https://key.wilsoz.com/if/admin/&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;PostgreSQL Docs:&amp;#039;&amp;#039;&amp;#039; https://www.postgresql.org/docs/16/&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;OAuth 2.0 Spec:&amp;#039;&amp;#039;&amp;#039; https://datatracker.ietf.org/doc/html/rfc6749&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;OpenID Connect Spec:&amp;#039;&amp;#039;&amp;#039; https://openid.net/connect/&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Related Services ==&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;OpenBao&amp;#039;&amp;#039;&amp;#039; (secrets store): &amp;lt;code&amp;gt;/library/openbao-app/&amp;lt;/code&amp;gt; — stores API tokens&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Stalwart&amp;#039;&amp;#039;&amp;#039; (mail server): Uses LDAP for user lookups&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Nextcloud&amp;#039;&amp;#039;&amp;#039; (file sync): Uses LDAP for CalDAV/CardDAV&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;n8n&amp;#039;&amp;#039;&amp;#039; (automation): Uses OAuth for form access control&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;PostgreSQL&amp;#039;&amp;#039;&amp;#039; (database): Internal to Authentik&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Nginx&amp;#039;&amp;#039;&amp;#039; (reverse proxy): VPS nginx forwards key.wilsoz.com traffic here&lt;/div&gt;</summary>
		<author><name>Wikibot</name></author>
	</entry>
</feed>