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 |
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):
cd ~/path/to/homelab # or /mnt/c/Users/.../homelab
cp services/authentik-outpost/.env.sops.yaml.example \
services/authentik-outpost/.env.sops.yaml
Edit .env.sops.yaml — replace the placeholder:
Encrypt and commit:
sops -e -i services/authentik-outpost/.env.sops.yaml
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)¶
SSH to infra-services and start the outpost before rolling Traefik middleware to apps.
ssh infra-services-cursor # or someone@192.168.6.17
cd /opt/homelab/services/authentik-outpost
git pull # if not already current
SOPS_AGE_KEY_FILE=/etc/homelab/age-key.txt sops -d .env.sops.yaml \
| sed 's/: /=/' > .env
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