# Authentik OIDC backend # ArgoCD-managed. BlueJay Lab. # # Stack: # - PostgreSQL 16 StatefulSet (single replica, Longhorn RWO 5Gi) # - Redis 7 Deployment (no persistence — session/cache only) # - Authentik server + worker Deployments (image ghcr.io/goauthentik/server:2024.12.3) # - Media PVC shared between server + worker (Longhorn RWO 2Gi) # - Certificate via step-ca-acme ClusterIssuer # - Traefik IngressRoute at id.iamworkin.lan # # Secrets come from 1Password item "authentik-credentials" (IAmWorkin vault, id y6i74ch22q5wvm7znquq4nhhcu) # via the OnePasswordItem CRD, materialized into k8s Secret authentik/authentik-credentials. # # Why the discovery URL is /application/o/pimanager/ : Authentik issues per-application OIDC providers. # The pimanager OIDC application/provider is created after the cluster pods are healthy (manual or # via API once the bootstrap token is available — see Notes substrate). --- apiVersion: v1 kind: Namespace metadata: name: authentik labels: app.kubernetes.io/part-of: bluejay-infra --- # 1Password operator pulls the authentik-credentials item into a k8s Secret of the same name. # Field labels in 1P become Secret keys: AUTHENTIK_SECRET_KEY, POSTGRES_PASSWORD, REDIS_PASSWORD, # BOOTSTRAP_ADMIN_PASSWORD, BOOTSTRAP_ADMIN_TOKEN, BOOTSTRAP_ADMIN_EMAIL. apiVersion: onepassword.com/v1 kind: OnePasswordItem metadata: name: authentik-credentials namespace: authentik spec: itemPath: "vaults/IAmWorkin/items/authentik-credentials" --- # Shared media volume for server + worker pods. apiVersion: v1 kind: PersistentVolumeClaim metadata: name: authentik-media namespace: authentik spec: storageClassName: longhorn accessModes: [ReadWriteOnce] resources: requests: storage: 2Gi --- # PostgreSQL 16 StatefulSet — Authentik's primary store. apiVersion: apps/v1 kind: StatefulSet metadata: name: authentik-postgres namespace: authentik labels: app: authentik-postgres argocd.argoproj.io/instance: infra-authentik spec: persistentVolumeClaimRetentionPolicy: whenDeleted: Retain whenScaled: Retain podManagementPolicy: OrderedReady serviceName: authentik-postgres replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: app: authentik-postgres template: metadata: labels: app: authentik-postgres spec: containers: - name: postgres image: postgres:16-alpine ports: - containerPort: 5432 name: postgres env: - name: POSTGRES_USER value: authentik - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: authentik-credentials key: POSTGRES_PASSWORD - name: POSTGRES_DB value: authentik - name: POSTGRES_INITDB_ARGS value: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - name: PGDATA value: /var/lib/postgresql/data/pgdata readinessProbe: exec: command: ["pg_isready", "-U", "authentik"] initialDelaySeconds: 5 periodSeconds: 5 livenessProbe: exec: command: ["pg_isready", "-U", "authentik"] initialDelaySeconds: 30 periodSeconds: 30 resources: requests: { cpu: 100m, memory: 256Mi } limits: { cpu: 1000m, memory: 1Gi } volumeMounts: - name: pgdata mountPath: /var/lib/postgresql/data volumeClaimTemplates: - metadata: name: pgdata spec: storageClassName: longhorn accessModes: [ReadWriteOnce] volumeMode: Filesystem resources: requests: storage: 5Gi --- apiVersion: v1 kind: Service metadata: name: authentik-postgres namespace: authentik spec: clusterIP: None selector: app: authentik-postgres ports: - name: postgres port: 5432 targetPort: 5432 --- # Redis 7 — session storage + Celery broker. No persistence needed (cache). apiVersion: apps/v1 kind: Deployment metadata: name: authentik-redis namespace: authentik labels: app: authentik-redis argocd.argoproj.io/instance: infra-authentik spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: authentik-redis template: metadata: labels: app: authentik-redis spec: containers: - name: redis image: redis:7-alpine args: - "--save" - "" - "--appendonly" - "no" - "--requirepass" - "$(REDIS_PASSWORD)" env: - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: authentik-credentials key: REDIS_PASSWORD ports: - containerPort: 6379 name: redis readinessProbe: tcpSocket: { port: 6379 } initialDelaySeconds: 5 periodSeconds: 5 livenessProbe: tcpSocket: { port: 6379 } initialDelaySeconds: 30 periodSeconds: 30 resources: requests: { cpu: 50m, memory: 64Mi } limits: { cpu: 500m, memory: 256Mi } --- apiVersion: v1 kind: Service metadata: name: authentik-redis namespace: authentik spec: selector: app: authentik-redis ports: - name: redis port: 6379 targetPort: 6379 --- # Authentik server Deployment — HTTP frontend on :9000. apiVersion: apps/v1 kind: Deployment metadata: name: authentik-server namespace: authentik labels: app: authentik-server argocd.argoproj.io/instance: infra-authentik spec: replicas: 1 strategy: type: Recreate # shares /media RWO PVC with worker selector: matchLabels: app: authentik-server template: metadata: labels: app: authentik-server spec: securityContext: # Authentik image runs as uid 1000 "authentik" but the Longhorn PVC mounts # root:root by default. fsGroup recursively chgrp + chmod g+rwx so the # non-root container can mkdir /media/public during the tenant_files migration. fsGroup: 1000 containers: - name: server image: ghcr.io/goauthentik/server:2024.12.3 args: ["server"] ports: - containerPort: 9000 name: http - containerPort: 9443 name: https env: - name: AUTHENTIK_SECRET_KEY valueFrom: secretKeyRef: name: authentik-credentials key: AUTHENTIK_SECRET_KEY - name: AUTHENTIK_REDIS__HOST value: authentik-redis - name: AUTHENTIK_REDIS__PASSWORD valueFrom: secretKeyRef: name: authentik-credentials key: REDIS_PASSWORD - name: AUTHENTIK_POSTGRESQL__HOST value: authentik-postgres - name: AUTHENTIK_POSTGRESQL__NAME value: authentik - name: AUTHENTIK_POSTGRESQL__USER value: authentik - name: AUTHENTIK_POSTGRESQL__PASSWORD valueFrom: secretKeyRef: name: authentik-credentials key: POSTGRES_PASSWORD - name: AUTHENTIK_BOOTSTRAP_PASSWORD valueFrom: secretKeyRef: name: authentik-credentials key: BOOTSTRAP_ADMIN_PASSWORD - name: AUTHENTIK_BOOTSTRAP_TOKEN valueFrom: secretKeyRef: name: authentik-credentials key: BOOTSTRAP_ADMIN_TOKEN - name: AUTHENTIK_BOOTSTRAP_EMAIL valueFrom: secretKeyRef: name: authentik-credentials key: BOOTSTRAP_ADMIN_EMAIL - name: AUTHENTIK_DISABLE_UPDATE_CHECK value: "true" - name: AUTHENTIK_ERROR_REPORTING__ENABLED value: "false" - name: AUTHENTIK_LOG_LEVEL value: info readinessProbe: httpGet: path: /-/health/ready/ port: 9000 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 livenessProbe: httpGet: path: /-/health/live/ port: 9000 initialDelaySeconds: 60 periodSeconds: 30 timeoutSeconds: 10 failureThreshold: 3 resources: requests: { cpu: 150m, memory: 512Mi } limits: { cpu: 1500m, memory: 1Gi } volumeMounts: - name: media mountPath: /media volumes: - name: media persistentVolumeClaim: claimName: authentik-media --- # Authentik worker Deployment — runs Celery background tasks. apiVersion: apps/v1 kind: Deployment metadata: name: authentik-worker namespace: authentik labels: app: authentik-worker argocd.argoproj.io/instance: infra-authentik spec: replicas: 1 strategy: type: Recreate # shares /media RWO PVC with server selector: matchLabels: app: authentik-worker template: metadata: labels: app: authentik-worker spec: securityContext: # Same as server pod — non-root uid 1000 needs PVC group write. fsGroup: 1000 containers: - name: worker image: ghcr.io/goauthentik/server:2024.12.3 args: ["worker"] env: - name: AUTHENTIK_SECRET_KEY valueFrom: secretKeyRef: name: authentik-credentials key: AUTHENTIK_SECRET_KEY - name: AUTHENTIK_REDIS__HOST value: authentik-redis - name: AUTHENTIK_REDIS__PASSWORD valueFrom: secretKeyRef: name: authentik-credentials key: REDIS_PASSWORD - name: AUTHENTIK_POSTGRESQL__HOST value: authentik-postgres - name: AUTHENTIK_POSTGRESQL__NAME value: authentik - name: AUTHENTIK_POSTGRESQL__USER value: authentik - name: AUTHENTIK_POSTGRESQL__PASSWORD valueFrom: secretKeyRef: name: authentik-credentials key: POSTGRES_PASSWORD - name: AUTHENTIK_DISABLE_UPDATE_CHECK value: "true" - name: AUTHENTIK_ERROR_REPORTING__ENABLED value: "false" - name: AUTHENTIK_LOG_LEVEL value: info resources: requests: { cpu: 100m, memory: 256Mi } limits: { cpu: 1000m, memory: 768Mi } volumeMounts: - name: media mountPath: /media volumes: - name: media persistentVolumeClaim: claimName: authentik-media --- apiVersion: v1 kind: Service metadata: name: authentik-server namespace: authentik spec: selector: app: authentik-server ports: - name: http port: 9000 targetPort: 9000 - name: https port: 9443 targetPort: 9443 --- # step-ca leaf certificate for id.iamworkin.lan. # step-ca container resolver uses pfSense Unbound, so the public A record for id.iamworkin.lan # MUST exist before this Certificate is applied (cert-manager HTTP-01 will silently 2h-backoff # otherwise). Added 2026-05-25 via scripts/pfsense-add-id-host.py. apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: authentik-tls namespace: authentik spec: secretName: authentik-tls dnsNames: - id.iamworkin.lan issuerRef: name: step-ca-acme kind: ClusterIssuer --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: authentik namespace: authentik spec: entryPoints: [websecure] routes: - match: Host(`id.iamworkin.lan`) kind: Rule services: - name: authentik-server port: 9000 tls: secretName: authentik-tls