Authentik admin setup — infra-services outpost (ADR-002)¶
Owner checklist for configuring Authentik before enabling forward-auth on
*.infra.realemail.app. Repo-side Traefik/outpost wiring lives in
authentik-cross-host-sso.md and
services/authentik-outpost/README.md.
Time: ~20–30 minutes first time
Admin URL: https://auth.realemail.app/if/admin/
Identity server: saltierpoop (Saltbox) — stays where it is
New outpost host: infra-services (192.168.6.17) — homelab-managed, not Saltbox
What you are building¶
Browser → infra Traefik (*.infra.realemail.app)
│
├─ forwardAuth → authentik-outpost container (infra-services :9000)
│ │
│ └─ API/websocket → auth.realemail.app (saltierpoop)
│
└─ after login → upstream app (Grafana, Komodo, …)
You will create three things in Authentik admin:
- Proxy provider (domain-level forward auth for the infra zone)
- Application bound to that provider
- Manual proxy outpost whose token the infra container uses to register
Do not attach infra apps to Saltbox’s embedded outpost — that outpost runs on
saltierpoop and serves *.realemail.app. Infra needs its own outpost on infra-services.
Pre-flight¶
- [ ] You can log in to Authentik admin at
https://auth.realemail.app/if/admin/ - [ ] Homelab PR with
services/authentik-outpost/and Traefik middleware is merged or ready on the branch you will deploy - [ ] You have WSL (or another environment) with
sopsand the operator age key for encrypting the outpost token - [ ] You understand deploy order: Authentik admin → SOPS token → start outpost → redeploy Traefik/app stacks (turning on middleware before the outpost is running breaks every infra UI)
Hostnames in scope (initial rollout)¶
These infra Traefik routers will use authentik@file once deployed:
| Hostname | Service |
|---|---|
https://traefik.infra.realemail.app |
Traefik dashboard |
https://grafana.infra.realemail.app |
Grafana |
https://prometheus.infra.realemail.app |
Prometheus |
https://alertmanager.infra.realemail.app |
Alertmanager |
https://komodo.infra.realemail.app |
Komodo (native OIDC — not forward-auth) |
https://homepage.infra.realemail.app |
Homepage |
https://ara.infra.realemail.app |
ARA |
https://adguard.infra.realemail.app |
AdGuard Home |
https://wazuh.infra.realemail.app |
Wazuh dashboard (retired; stack removed) |
https://paperless.infra.realemail.app |
Paperless-ngx |
https://paperless-gpt.infra.realemail.app |
Paperless-GPT |
https://stirling.infra.realemail.app |
Stirling-PDF |
Explicitly out of scope: https://plex.realemail.app (ADR-002 exception — no Authentik at the reverse proxy).
Recommended vs alternative¶
| Approach | When to use |
|---|---|
| Forward auth (domain level) — recommended | One provider covers all *.infra.realemail.app; least admin overhead |
| Forward auth (single application) | Different users/policies per app (e.g. only ops sees Prometheus) |
This runbook uses domain level. See Alternative: per-app providers at the end if you need stricter per-app authorization.
Step 1 — Create application + domain-level proxy provider¶
Authentik’s combined wizard is the easiest path.
- Open Applications → Applications
- Click Create with provider (or New application → wizard with provider)
- Application step
- Name:
Infra homelab (domain)(any clear label) - Slug:
infra-homelab-domain(auto-filled is fine) - Launch URL:
https://homepage.infra.realemail.app(convenience link only) - Click Next
- Choose provider type
- Select Proxy Provider
- Click Next
- Configure Proxy Provider
- Name:
infra-realemail-app-domain - Authorization flow:
default-provider-authorization-implicit-consent(or your standard “login + consent” flow — match what Saltbox apps use if unsure) - Authentication flow: leave default / same as other Saltbox forward-auth apps
- Mode: Forward auth (domain level)
- Authentication URL:
https://homepage.infra.realemail.app(must be a hostname on the infra zone served by infra Traefik + infra outpost — see Step 2b. Do not usehttps://auth.realemail.apphere; that splits OAuth session from callback outpost.) - Cookie domain:
infra.realemail.appParent domain shared by all protected infra hostnames (no leading*.— Authentik expects the registrable parent, e.g.infra.realemail.appnotgrafana.infra.realemail.app). - External host: leave empty for domain-level mode (not used the same way as single-app mode)
- Internal host: leave empty (Traefik forwards to real apps; outpost only checks auth)
- Unauthenticated paths / URLs: leave empty unless you later need scrape/webhook exceptions (Prometheus scrapes are server-side — they do not hit Traefik forward-auth in the browser path)
- Click Next
- Review → Submit
You should land on the new Application page with the provider attached.
Sanity-check the provider¶
- Applications → Providers → open
infra-realemail-app-domain - Confirm Mode =
Forward auth (domain level) - Open the Authentication tab — note the Client ID (useful for log correlation later)
Official reference:
Step 2 — Create a manual proxy outpost (infra-services)¶
Saltbox already manages Docker on saltierpoop. The infra outpost is homelab compose on another host, so use manual integration — Authentik must not try to spawn the container on saltierpoop.
- Applications → Outposts
- Click Create
- Set fields:
| Field | Value |
|---|---|
| Name | infra-services |
| Type | Proxy |
| Integration | ---- / None / manual (wording varies by version — pick the option that does not deploy via Docker/K8s) |
| Applications | Select Infra homelab (domain) (the app from Step 1) |
| Configuration → authentik_host | https://auth.realemail.app (usually inherited; verify in Advanced settings if present) |
| Configuration → authentik_host_insecure | false |
- Click Create
Copy the outpost token¶
Immediately after creation:
- Open the new infra-services outpost
- Find Token / View deployment info / Outpost service connection (label varies by Authentik version)
- Copy the token — it is shown once. Store it in your password manager until SOPS is done.
If you lose it: delete and recreate the outpost, or rotate the service-account token in Authentik’s service account settings (more steps — recreating the outpost is usually faster for a greenfield setup).
Do not use Saltbox Docker integration
If you pick Docker integration on the saltierpoop-hosted Authentik, it will attempt to
manage containers on saltierpoop’s Docker socket. The infra outpost must run on
infra-services via services/authentik-outpost/compose.yml.
Step 2b Authentication URL must be on the infra zone (required)¶
When Authentication URL is https://auth.realemail.app, OAuth callbacks land on saltierpoop:
That hits the Embedded Outpost, but the OAuth session was started by the infra-services
outpost when you first opened komodo.infra.realemail.app. Outposts do not share session state.
Saltierpoop logs show the failure clearly:
Do not assign the infra app to the Embedded Outpost as a workaround — that breaks Saltbox
*.realemail.app forward-auth (404 storms on sonarr, tdarr, etc.) and still will not share
sessions with the remote outpost.
Fix: edit the proxy provider (infra-realemail-app-domain):
| Field | Wrong | Correct |
|---|---|---|
| Authentication URL | https://auth.realemail.app |
https://homepage.infra.realemail.app (any stable infra hostname) |
| Cookie domain | infra.realemail.app |
unchanged |
After save, OAuth callback becomes:
Infra Traefik routes that to the same infra-services outpost that started the flow.
Users still log in at auth.realemail.app for credentials; only the callback host moves to
the infra zone.
Embedded Outpost: leave it with Saltbox apps only — remove Infra homelab (domain) if you added it during troubleshooting.
Wait ~30 seconds after provider save, then test incognito → https://komodo.infra.realemail.app.
Step 3 — Optional access control (policies)¶
Domain-level forward auth applies one authorization policy to all infra hostnames. You cannot assign different Authentik apps/policies per hostname in this mode.
For a homelab where every infra UI is ops-only, bind the application to a group:
- Applications → Applications → Infra homelab (domain)
- Policy / Group / User Bindings → Bind existing policy or Create binding
- Example: require membership in group
homelab-admins(create the group under Directory → Groups if needed) - Policy engine mode:
anyorallper your usual pattern
Skip this step if “any logged-in Authentik user may access infra UIs” is acceptable for now.
Step 4 — Save the token (SOPS)¶
The outpost container reads AUTHENTIK_TOKEN from a decrypted .env on infra-services.
On your dev machine (WSL), use the operator key to edit the encrypted source. Only create it if absent, using the example as a schema; never copy the example over an existing source during repeat setup. SOPS encrypts on save:
Set the string value in the SOPS editor (do not save a separate plaintext file):
After saving encrypted, commit only the source:
git add services/authentik-outpost/.env.sops.yaml
git commit -m "Add Authentik infra outpost token"
git push
Details: secrets.md § Decrypting SOPS YAML to dotenv
Never commit plaintext tokens
Only the encrypted .env.sops.yaml belongs in git. .env on the host is gitignored.
Step 5 — Deploy on infra-services (after git pull)¶
After owner-approved deployment, connect separately to infra-services (for example,
ssh infra-services-cursor). Run the complete block below in the target Bash
shell, not after an SSH command that can return to the local shell. Start the
outpost before rolling Traefik middleware to apps. Follow the
rendering contract: Python 3/SOPS,
reviewed full checkout, trusted directory writers and authorized noninteractive
sudo. Bootstrap installs the automation key as root:root 0400; do not relax it.
Render and Compose below run as root; .env is root-owned 0600. Decryption or
conversion failure preserves the old env, not permission to deploy stale values.
Stop on any render failure; see the post-promotion interruption caveat in the contract.
Synthetic tests establish source behavior only, not live access/client acceptance.
set -euo pipefail
cd /opt/homelab/services/authentik-outpost || exit 1
git pull --ff-only || exit 1 # if not already current
sudo -n python3 /opt/homelab/scripts/render-sops-env.py \
--source .env.sops.yaml --output .env --require AUTHENTIK_TOKEN || exit 1
sudo -n docker compose up -d
docker logs authentik-outpost --tail 30
Healthy logs mention successful connection to Authentik and provider config loaded. Errors about invalid token → re-check Step 2 token and SOPS file.
Then redeploy Traefik and stacks (Komodo deploy-infra or manual docker compose up -d per stack).
Step 6 — Verify in Authentik admin¶
- Applications → Outposts → infra-services
- Status should show healthy / recent websocket ping (may take 1–2 minutes after container start)
- If unhealthy:
docker logs authentik-outposton infra-services- Search saltierpoop Authentik server logs for the provider Client ID from Step 1
- Confirm infra-services can reach
https://auth.realemail.app(no firewall block from VLAN 6 → saltierpoop)
Step 7 — Verify in the browser¶
Use a private/incognito window for each check.
| # | URL | Expected |
|---|---|---|
| 1 | https://grafana.infra.realemail.app |
Redirect to Authentik login → after login, Grafana loads |
| 2 | https://komodo.infra.realemail.app |
Same SSO session (cookie on infra.realemail.app) — should not ask to “authorize app” again |
| 3 | https://homepage.infra.realemail.app |
Loads after auth |
| 4 | https://plex.realemail.app |
No Authentik redirect — Plex loads as before |
Logout (domain-level):
https://auth.realemail.app/outpost.goauthentik.io/sign_out
(invalidates sessions for providers using that authentication URL — see Authentik proxy docs)
Troubleshooting¶
| Symptom | Check |
|---|---|
| All infra URLs → 502 Bad Gateway | docker ps on infra-services — is authentik-outpost running and on network traefik? |
| 401, no redirect to login | Provider not assigned to infra-services outpost; or middleware deployed before outpost |
| Redirect loop | Cookie domain must be infra.realemail.app; clock skew between hosts; clear cookies for *.infra.realemail.app |
| Login works for one hostname only | You used single-app mode by mistake, or multiple conflicting providers |
400 on auth.realemail.app/outpost.goauthentik.io/callback?... after login |
Authentication URL on auth.realemail.app — session on infra outpost, callback on embedded. Fix: Step 2b; remove infra app from Embedded Outpost |
Saltbox *.realemail.app apps broken (404 on forward-auth) |
Infra app was added to Embedded Outpost — remove it; restart Saltbox authentik/traefik if needed |
| Outpost unhealthy in admin | Wrong token; AUTHENTIK_HOST must be https://auth.realemail.app; TLS interception |
| SSO works on saltierpoop but not infra | Expected separate cookie zones unless you deliberately unify — see ADR-002 |
| Grafana loads but “login” still shown inside app | Configure Grafana auth proxy (repo: services/monitoring/compose.yml) |
| Komodo asks for username/password after Authentik | Komodo uses native OIDC, not forward-auth — see komodo-authentik-oidc.md |
Outpost metrics (optional): port 9300/metrics inside the container — not published to the host by default.
Adding a new *.infra.realemail.app service later¶
Domain-level provider (this runbook):
- Add Traefik labels including
traefik.http.routers.<name>.middlewares=authentik@file - Deploy the stack — no Authentik admin change required
- Verify in incognito
If you switched to per-app providers: repeat Step 1 for each new hostname and add the app to the infra-services outpost.
Checklist also in adding-a-service.md.
Alternative: per-app providers¶
Use when Prometheus should be restricted to a subset of users while Grafana is wider, etc.
For each hostname:
- Applications → Create with provider
- Mode: Forward auth (single application)
- External host:
https://<app>.infra.realemail.app(exact URL) - Create matching Application
- Outposts → infra-services → Applications: add each new application
- Traefik callback router in
services/traefik/config/dynamic/authentik.ymlalready covers all*.infra.realemail.apppaths under/outpost.goauthentik.io/
More admin work; finer-grained policies per app.
Token rotation¶
- Applications → Outposts → infra-services — regenerate token (or recreate outpost)
- Update
services/authentik-outpost/.env.sops.yaml, re-encrypt, commit, push - On infra-services: decrypt
.envagain,docker compose up -d --force-recreate - Confirm outpost healthy in admin
Related¶
- ADR-002: Authentik universal SSO
- authentik-cross-host-sso.md — repo wiring summary
- dr-public-edge.md — Authentik DB backup/restore on saltierpoop
- Authentik Traefik integration
- Authentik outposts