28 KiB
Deploying Authentik (guest-relations) & Smallstep step-ca (turnstile)
Overview
This guide documents the full deployment process for two services in the mk-labs homelab:
- Authentik — Identity provider and SSO platform, running on
guest-relations(10.1.71.40) - Smallstep step-ca — SSH Certificate Authority, running on
turnstile(10.1.71.34)
Both follow the same provisioning pattern: Terraform creates the VM → Ansible configures the OS and deploys the application → Traefik provides reverse proxy and TLS termination (Authentik only; step-ca is accessed directly) → Authentik OIDC applications are codified as blueprints.
Prerequisites:
- Proxmox cluster (
magic-kingdom) operational city-hallVM with Terraform CLI, Ansible CLI, Boilerplates CLI, and GitHub CLI (gh) installedlightning-lane(10.1.71.35) running Traefik with Cloudflare DNS-01 wildcard certsmonorail(10.1.71.20) running Technitium DNS forlocal.mk-labs.cloud- Ansible vault configured at
ansible/group_vars/all/vault
Part 1: Deploying Authentik (guest-relations)
Authentik is a Tier 1 service — it runs outside Kubernetes because other services depend on it for authentication. As of version 2025.10, Redis is no longer required — all caching, tasks, and WebSocket connections have been migrated to PostgreSQL. It runs three containers: server, worker, and PostgreSQL.
1.1 Create the Terraform tfvars
Create terraform/proxmox/hosts/tfvars/guest-relations.tfvars:
# guest-relations — Authentik Identity Provider / SSO
# VM ID convention: third_octet × 1000 + fourth_octet = 71040
hostname = "guest-relations"
vm_id = 71040
ip_address = "10.1.71.40"
target_node = "main-street-usa"
template_name = "ubuntu-24.04-medium"
# Bootstrap mode — no n8n/NetBox pipeline available yet
started = true
use_dhcp = false
Note: The medium template (2 vCPU / 4GB RAM / 16GB disk) is used because Authentik runs PostgreSQL alongside the application containers.
1.2 Provision the VM with Terraform
cd terraform/proxmox/hosts
terraform apply \
-var-file="tfvars/guest-relations.tfvars" \
-state="states/guest-relations.tfstate" \
-var="proxmox_api_url=https://main-street-usa.local.mk-labs.cloud:8006" \
-var="proxmox_api_token=terraform@pve!automation=<token>"
After Terraform completes, create a DHCP reservation in UniFi for 10.1.71.40 and start the VM.
1.3 Create DNS records on monorail
On monorail (Technitium DNS), create:
- A record:
guest-relations.local.mk-labs.cloud→10.1.71.40(direct access) - A record:
authentik.local.mk-labs.cloud→10.1.71.35(points to lightning-lane for Traefik routing)
Critical rule: All services behind Traefik must have their service DNS pointing to
lightning-lane(10.1.71.35), not the backend VM's IP. Traefik routes based on theHost()header.
1.4 Generate the Compose stack with Boilerplates
On city-hall, generate the Authentik boilerplate:
boilerplates compose generate authentik
Use these settings during generation:
traefik_enabled: false(Authentik is on a different VM than Traefik, so Docker labels won't work — we use Traefik's dynamic file config instead)
The boilerplate auto-generates three secrets:
AUTHENTIK_SECRET_KEYDATABASE_PASSWORDAUTHENTIK_ADMIN_PASSWORD
Save these somewhere safe — the admin password is needed for first login.
Commit the generated files to boilerplates/authentik/ at the repo root. Review the compose.yaml for any None placeholder values left by the boilerplate (timezone, network names, etc.) and fix them.
Post-2025.10: If the boilerplate generates a Redis service, remove it. Authentik 2025.10+ no longer requires Redis. If upgrading from an older version and you see an orphan
authentik-rediscontainer warning, clean it up withdocker compose down --remove-orphans && docker compose up -d.
1.5 Store secrets in Ansible Vault
Add Authentik secrets and OIDC blueprint credentials to ansible/group_vars/all/vault:
ansible-vault edit ansible/group_vars/all/vault
Add:
# Authentik application secrets
authentik_secret_key: "<generated-secret-key>"
authentik_admin_password: "<generated-admin-password>"
authentik_database_password: "<generated-database-password>"
# OIDC client credentials for blueprints (pre-generated)
vault_proxmox_oidc_client_id: "<pre-generated-client-id>"
vault_proxmox_oidc_client_secret: "<pre-generated-client-secret>"
vault_stepca_oidc_client_id: "<pre-generated-client-id>"
vault_stepca_oidc_client_secret: "<pre-generated-client-secret>"
Pre-generate client IDs and secrets:
python3 -c "
import secrets, string
def gen_id(): return secrets.token_hex(20)
def gen_secret(): return ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(128))
print(f'Client ID: {gen_id()}')
print(f'Client Secret: {gen_secret()}')
"
1.6 Create Authentik Blueprints
Blueprints are declarative YAML files that codify Authentik OIDC providers and applications. They are mounted into the Authentik container and applied automatically.
Critical: Blueprint files must use the
.yamlextension, not.yml. Authentik's blueprint discovery only scans for.yamlfiles.
Critical: Use the
!EnvYAML tag to reference environment variables in blueprints, not${VAR}shell-style substitution. Example:client_id: !Env STEPCA_OIDC_CLIENT_ID
Create boilerplates/authentik/blueprints/step-ca.yaml:
version: 1
metadata:
name: mk-labs-step-ca
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
- model: authentik_providers_oauth2.oauth2provider
id: step-ca-provider
state: present
identifiers:
name: "Provider for step-ca"
attrs:
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
client_type: confidential
client_id: !Env STEPCA_OIDC_CLIENT_ID
client_secret: !Env STEPCA_OIDC_CLIENT_SECRET
redirect_uris:
- url: "http://127.0.0.1:10000"
matching_mode: strict
sub_mode: hashed_user_id
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
- model: authentik_core.application
id: step-ca-application
state: present
identifiers:
slug: step-ca
attrs:
name: "step-ca"
provider: !KeyOf step-ca-provider
group: "Infrastructure"
policy_engine_mode: any
Create boilerplates/authentik/blueprints/proxmox.yaml following the same pattern, with PROXMOX_OIDC_CLIENT_ID / PROXMOX_OIDC_CLIENT_SECRET env vars and redirect URIs for each Proxmox node (https://<hostname>:8006, no trailing slash).
1.7 Update the Compose file for blueprints
The compose.yaml needs two additions to both the authentik (server) and authentik_worker services:
- A volume mount for custom blueprints:
./blueprints:/blueprints/custom:ro - Environment variables for OIDC credentials consumed by blueprints via
!Env:
environment:
# ... existing vars ...
- PROXMOX_OIDC_CLIENT_ID=${PROXMOX_OIDC_CLIENT_ID}
- PROXMOX_OIDC_CLIENT_SECRET=${PROXMOX_OIDC_CLIENT_SECRET}
- STEPCA_OIDC_CLIENT_ID=${STEPCA_OIDC_CLIENT_ID}
- STEPCA_OIDC_CLIENT_SECRET=${STEPCA_OIDC_CLIENT_SECRET}
volumes:
# ... existing volumes ...
- ./blueprints:/blueprints/custom:ro
The .env file (templated by Ansible) provides the actual values from vault.
1.8 Create the Ansible role and inventory
Inventory entry — add to ansible/inventory.yml:
authentik_server:
hosts:
guest_relations:
ansible_host: 10.1.71.40
ansible_become: true
Host vars — create ansible/host_vars/guest_relations/vars:
---
ip_address: 10.1.71.40
app_role: identity_provider
app_name: authentik
app_deployment: docker_compose
Ansible role — create ansible/roles/authentik/ with:
tasks/main.yml— creates the application directory, copiescompose.yamlfromboilerplates/authentik/, syncs theblueprints/directory, templates the.envfile with vault secrets including OIDC credentials, and starts the stackdefaults/main.yml— setsauthentik_base_dir: /opt/docker/authentikhandlers/main.yml—restart authentikhandler usingdocker_compose_v2templates/env.j2— injects all secrets from vault including blueprint OIDC credentials
Playbook — create ansible/playbooks/deploy_authentik.yml:
---
- name: Deploy Authentik identity provider
hosts: authentik_server
become: true
roles:
- common
- docker-host
- authentik
1.9 Deploy with Ansible
cd ansible
ansible-playbook -i inventory.yml playbooks/deploy_authentik.yml
This runs three roles in sequence: common (hostname, timezone, base packages, Chrony NTP pointing to sundial) → docker-host (Docker CE, adds wed user to docker group) → authentik (Compose stack + blueprints).
Authentik will discover and apply the blueprints within a minute of startup. Verify in the admin UI under Customization → Blueprints and Applications → Applications.
1.10 Create the Traefik dynamic config
Create boilerplates/traefik/dynamic/authentik.yml:
http:
routers:
authentik:
rule: "Host(`authentik.local.mk-labs.cloud`)"
entryPoints:
- websecure
service: authentik
tls:
certResolver: cloudflare
middlewares:
- security-headers
services:
authentik:
loadBalancer:
servers:
- url: "http://10.1.71.40:8000"
middlewares:
authentik-forwardauth:
forwardAuth:
address: "http://10.1.71.40:8000/outpost.goauthentik.io/auth/nginx"
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- X-authentik-jwt
- X-authentik-meta-jwks
- X-authentik-meta-outpost
- X-authentik-meta-provider
- X-authentik-meta-app
- X-authentik-meta-version
Deploy to lightning-lane:
cd ansible
ansible-playbook -i inventory.yml playbooks/update_traefik_routes.yml
1.11 Verify the deployment
Check all three containers are healthy on guest-relations:
ssh wed@guest-relations.local.mk-labs.cloud docker ps
Expected output: authentik-server, authentik-worker, and authentik-postgres all with status Up ... (healthy).
Access https://authentik.local.mk-labs.cloud and log in with:
- Username:
akadmin - Password: the
AUTHENTIK_ADMIN_PASSWORDfrom vault
1.12 Create your user in Authentik
Do not use akadmin for day-to-day logins. Create a dedicated user:
- Log in as
akadminathttps://authentik.local.mk-labs.cloud/if/admin/ - Go to Directory → Users → Create
- Set username to
rblundon, fill in email and display name, mark as active - Set a password
- Optionally create a
proxmox-adminsgroup under Directory → Groups and addrblundonto it
Keep
rblundonas a regular user (not an Authentik admin). Useakadminfor Authentik administration,rblundonfor logging into downstream services. These are separate privilege domains.
Part 2: Configuring Proxmox OIDC with Authentik
This configures SSO for the magic-kingdom Proxmox cluster using Authentik as the OIDC identity provider. The realm configuration is cluster-wide (stored in /etc/pve/domains.cfg), so it only needs to run against one node.
Note: The Proxmox OIDC application is codified as a blueprint (see Part 1.6). The provider and application are created automatically when Authentik starts with the blueprint mounted. The steps below cover the Proxmox-side realm configuration only.
2.1 Store credentials in Ansible Vault
The Proxmox OIDC client credentials are the same pre-generated values used in the blueprint. Store them in ansible/group_vars/proxmox/vault.yml:
ansible-vault create group_vars/proxmox/vault.yml
vault_proxmox_oidc_client_id: "<same-client-id-as-blueprint>"
vault_proxmox_oidc_client_key: "<same-client-secret-as-blueprint>"
2.2 Run the Ansible playbook (first pass — realm only)
cd ansible
ansible-playbook -i inventory.yml playbooks/configure_proxmox_oidc.yml
The ACL task will fail on the first run. This is expected — the user rblundon@authentik doesn't exist in Proxmox until the first OIDC login (the realm is configured with --autocreate 1).
2.3 First OIDC login
- Open
https://main-street-usa.local.mk-labs.cloud:8006 - Change the realm dropdown to authentik
- Click Login — you'll be redirected to Authentik
- Sign in as
rblundon
Gotcha: If you're already logged into Authentik as
akadmin, you'll be auto-logged in as akadmin. Log out of Authentik first athttps://authentik.local.mk-labs.cloud/if/flow/default-invalidation-flow/or use an incognito window.
2.4 Run the Ansible playbook (second pass — ACL)
ansible-playbook -i inventory.yml playbooks/configure_proxmox_oidc.yml
The ACL task grants rblundon@authentik the Administrator role at path /.
2.5 Ansible implementation notes
- Tasks use
ansible.builtin.shell(notcommand) becausecommandsplits multi-word--scopesarguments into separate args, breakingpveum --username-claimis only valid onpveum realm add, notpveum realm modifyno_log: trueis set on realm tasks to keep the client secret out of Ansible output- The playbook targets only
main-street-usasince realm config is cluster-wide via pmxcfs
Part 3: Deploying step-ca (turnstile)
step-ca is a lightweight SSH Certificate Authority that issues short-lived certificates authenticated via OIDC through Authentik. It eliminates SSH key management — no more authorized_keys files across hosts.
History: The
turnstilehostname was originally assigned to Authentik. Authentik was renamed toguest-relations, freeingturnstilefor step-ca.
3.1 Create the Terraform tfvars
Create terraform/proxmox/hosts/tfvars/turnstile.tfvars:
# turnstile — Smallstep step-ca SSH Certificate Authority
# VM ID convention: 71000 + 34 = 71034
hostname = "turnstile"
vm_id = 71034
ip_address = "10.1.71.34"
target_node = "fantasyland"
clone_node = "fantasyland"
template_name = "ubuntu-24.04-small"
datastore = "liberty-tree"
vlan_id = 71
bridge = "vmbr0"
dns_servers = ["10.1.71.1"]
search_domain = "local.mk-labs.cloud"
3.2 Provision the VM with Terraform
cd terraform/proxmox/hosts
terraform apply \
-var-file="tfvars/turnstile.tfvars" \
-state="states/turnstile.tfstate" \
-var="proxmox_api_url=https://fantasyland.local.mk-labs.cloud:8006" \
-var="proxmox_api_token=terraform@pve!automation=<token>"
Create a DHCP reservation in UniFi for 10.1.71.34 and start the VM.
3.3 Create DNS record on monorail
- A record:
turnstile.local.mk-labs.cloud→10.1.71.34(direct access — step-ca is NOT behind Traefik)
Note: Unlike other services, step-ca is accessed directly on port 9000, not through Traefik. The
stepCLI needs to talk to the CA API with its own TLS trust chain.
3.4 Create the Ansible role and inventory
Vault secrets — create ansible/host_vars/turnstile/vault:
stepca_ca_password: "<strong-password>"
stepca_oidc_client_id: "<same-client-id-as-blueprint>"
stepca_oidc_client_secret: "<same-client-secret-as-blueprint>"
Host vars — create ansible/host_vars/turnstile/vars:
---
ip_address: 10.1.71.34
app_role: ssh_certificate_authority
app_name: step-ca
app_deployment: docker_compose
stepca_hostname: turnstile.local.mk-labs.cloud
stepca_dns_names: "turnstile.local.mk-labs.cloud,10.1.71.34"
stepca_ssh_enabled: true
stepca_listen_port: 9000
stepca_oidc_provisioner_name: authentik
stepca_oidc_configuration_endpoint: "https://authentik.local.mk-labs.cloud/application/o/step-ca/.well-known/openid-configuration"
stepca_oidc_listen_address: ":10000"
stepca_oidc_domains:
- "local.mk-labs.cloud"
- "protonmail.com"
Important: The
stepca_oidc_domainslist must include every email domain that users in Authentik have. step-ca validates the OIDC token's email claim against this list and rejects any domain not listed.
Ansible role includes templates/patch_oidc_provisioner.py.j2 which patches ca.json post-init to ensure the OIDC provisioner has correct domains, client ID, and secret. This is idempotent and eliminates the need for manual step ca provisioner add.
Init container gotcha: The step-ca data directory must be owned by UID 1000 (
stepuser), not root.
3.5 Deploy with Ansible
cd ansible
ansible-playbook -i inventory.yml playbooks/deploy_step_ca.yml
Note the CA fingerprint from the init output.
3.6 Bootstrap your workstation
step ca bootstrap --ca-url https://turnstile.local.mk-labs.cloud:9000 --fingerprint <ca-fingerprint>
Ensure your SSH agent is running:
eval "$(ssh-agent -s)"
3.7 Daily SSH workflow
step ssh login rblundon@local.mk-labs.cloud --provisioner authentik
This opens your browser to Authentik, you authenticate, and step-ca issues a short-lived SSH certificate (default 16 hours) loaded into your SSH agent.
Verify:
step ssh list
ssh-add -l
Part 4: Host enrollment for step-ca
Host enrollment configures VMs to trust SSH certificates signed by step-ca and to present their own CA-signed host certificates.
4.1 Group variables
Add to ansible/group_vars/all/:
# step-ca client configuration
step_ca_url: "https://turnstile.local.mk-labs.cloud:9000"
step_ca_fingerprint: "<root-ca-fingerprint>"
step_ca_provisioner_name: "admin"
# Principal-to-user mapping
step_ca_principal_mappings:
- local_user: wed
principals:
- ryan.blundon@protonmail.com
- ryan.blundon
- ryanblundon
- local_user: rblundon
principals:
- ryan.blundon@protonmail.com
- ryan.blundon
- ryanblundon
Why principal mappings? The SSH certificate contains principals derived from your Authentik email (e.g.,
ryan.blundon@protonmail.com). When youssh wed@host, sshd checks if any ofwed's authorized principals match the certificate. Without this mapping, certificate auth silently falls through to password auth.
4.2 What the enrollment role does
The step_ca_client.yml tasks:
- Install step CLI — from Smallstep's apt repository
- Bootstrap CA trust —
step ca bootstrapto trust the CA's root certificate - Configure user certificate trust — writes SSH user CA public key, adds
TrustedUserCAKeysto sshd_config - Create authorized principals directory —
/etc/ssh/auth_principals/ - Create authorized principals files — maps certificate principals to local users (e.g.,
/etc/ssh/auth_principals/wed) - Configure AuthorizedPrincipalsFile — adds
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%uto sshd_config - Sign host certificate — generates a one-time token (delegated to Ansible controller) and signs the host's ECDSA key
- Configure sshd for host certificate — adds
HostCertificateandHostKeydirectives - Automated renewal — systemd timer for weekly host certificate renewal using SSHPOP
4.3 Enroll a host
cd ansible
ansible-playbook -i inventory.yml playbooks/deploy_common.yml -e target=scrim
Note: The enrollment role requires Ubuntu (uses apt). Fedora hosts will fail on the step CLI install task.
Note: The
stepCLI must be installed and bootstrapped on the Ansible controller (city-hall) for thedelegate_to: localhosttoken generation tasks.
4.4 Test SSH with certificates
step ssh login rblundon@local.mk-labs.cloud --provisioner authentik
ssh scrim
Verify certificate authentication:
ssh -v scrim 2>&1 | grep -E "Offering|certificate|CERT"
4.5 SSH config for short hostnames
If local.mk-labs.cloud is configured as a DNS search domain on your workstation, short hostnames resolve automatically.
Recommended ~/.ssh/config:
# mk-labs hosts — certificate auth via step-ca
Host *.local.mk-labs.cloud
User wed
# Short name resolution for mk-labs hosts
Host !10.1.71.* !*.local.mk-labs.cloud *
Match exec "nslookup %h.local.mk-labs.cloud >/dev/null 2>&1"
HostName %h.local.mk-labs.cloud
User wed
# Fallback key auth for hosts not yet enrolled
Host city-hall
HostName city-hall
User wed
IdentityFile ~/.ssh/ansible
Do NOT use
IdentitiesOnly yesfor mk-labs hosts — this prevents SSH from offering the certificate from the agent.
Appendix A: Troubleshooting
Authentik
Connection reset when hitting authentik.local.mk-labs.cloud — verify DNS resolves to 10.1.71.35 (lightning-lane), check Traefik dashboard for router errors, verify Traefik can reach backend.
Auto-logged in as akadmin instead of rblundon — log out of Authentik at the invalidation flow URL or use incognito.
Orphan Redis container warning — run docker compose down --remove-orphans && docker compose up -d. Redis was removed in Authentik 2025.10.
Authentik Blueprints
Blueprints not discovered — verify .yaml extension (not .yml). Check volume mount with docker exec authentik-worker ls -la /blueprints/custom/.
OIDC client ID is literal ${VAR} text — use !Env VAR_NAME YAML tag, not ${VAR_NAME}. Verify env vars in container with docker exec authentik-worker env | grep STEPCA.
Proxmox OIDC
"too many arguments" from pveum — use ansible.builtin.shell with single-quoted --scopes 'openid email profile'.
"Unknown option: username-claim" on modify — only valid on pveum realm add. Delete realm and re-add.
ACL update failed: user does not exist — expected on first run. Log in via OIDC first, then re-run.
Redirect URI mismatch — must exactly match including :8006 port and no trailing slash.
step-ca
"email is not allowed" — add the user's email domain to stepca_oidc_domains in host_vars.
"permission denied" during init — fix data directory ownership: sudo chown -R 1000:1000 /opt/docker/step-ca/data.
"no such file or directory: password" — secrets directory and password file must be created before init runs.
REMOTE HOST IDENTIFICATION HAS CHANGED — clear old key: ssh-keygen -f ~/.ssh/known_hosts -R <hostname>. Goes away permanently once host certificates are enrolled.
SSH Certificate Authentication
Certificate offered but server rejects it — check AuthorizedPrincipalsFile directive in sshd_config, verify /etc/ssh/auth_principals/<user> exists with matching principals, inspect cert with step ssh list --raw | step ssh inspect.
"Could not open a connection to your authentication agent" — start agent with eval "$(ssh-agent -s)", re-run step ssh login.
Short hostname asks for password but FQDN works — ensure search domain is configured, ensure no IdentitiesOnly yes blocking the agent cert.
Appendix B: File reference
homelab/
├── terraform/proxmox/hosts/
│ ├── tfvars/
│ │ ├── guest-relations.tfvars
│ │ └── turnstile.tfvars
│ └── states/ (gitignored)
├── boilerplates/
│ ├── authentik/
│ │ ├── compose.yaml (with blueprints volume mount)
│ │ └── blueprints/
│ │ ├── proxmox.yaml (must be .yaml not .yml)
│ │ └── step-ca.yaml (must be .yaml not .yml)
│ └── traefik/
│ └── dynamic/
│ └── authentik.yml
├── ansible/
│ ├── inventory.yml
│ ├── roles/
│ │ ├── common/
│ │ │ ├── tasks/
│ │ │ │ ├── main.yml
│ │ │ │ └── step_ca_client.yml
│ │ │ └── handlers/main.yml
│ │ ├── docker-host/
│ │ ├── authentik/
│ │ │ ├── tasks/main.yml
│ │ │ ├── defaults/main.yml
│ │ │ ├── handlers/main.yml
│ │ │ └── templates/env.j2
│ │ ├── step-ca/
│ │ │ ├── tasks/main.yml
│ │ │ ├── defaults/main.yml
│ │ │ ├── handlers/main.yml
│ │ │ └── templates/
│ │ │ ├── compose.yaml.j2
│ │ │ ├── env.j2
│ │ │ └── patch_oidc_provisioner.py.j2
│ │ └── proxmox/
│ │ ├── defaults/main.yml
│ │ └── tasks/
│ │ ├── main.yml
│ │ └── oidc.yml
│ ├── host_vars/
│ │ ├── guest_relations/vars
│ │ └── turnstile/
│ │ ├── vars
│ │ └── vault (encrypted)
│ ├── group_vars/
│ │ ├── all/
│ │ │ ├── vault (encrypted)
│ │ │ └── step_ca.yml
│ │ └── proxmox/
│ │ ├── oidc.yml
│ │ └── vault.yml (encrypted)
│ └── playbooks/
│ ├── deploy_authentik.yml
│ ├── deploy_step_ca.yml
│ ├── deploy_common.yml
│ ├── enroll_step_ca_client.yml
│ ├── configure_proxmox_oidc.yml
│ └── update_traefik_routes.yml
└── docs/guides/
└── service-deployment-authentik-and-step-ca.md
Appendix C: Key decisions and learnings
- Boilerplate-first for Compose — if a Boilerplates CLI template exists, use it. Ansible copies the generated files; it does not template them. Only the
.envis templated to inject vault secrets. step-ca has no boilerplate, so Ansible templates the compose file directly. - Traefik routing for cross-host services — Docker labels only work when the service and Traefik share a Docker network (same host). For services on separate VMs, use Traefik's dynamic file configuration with the backend's raw IP and port.
- DNS always points to lightning-lane — service hostnames resolve to
10.1.71.35, not to the backend VM. Exception: step-ca is accessed directly (port 9000) because thestepCLI needs to trust the CA's own certificate chain. - Two-pass playbook for Proxmox OIDC — the ACL task requires the user to exist in Proxmox, but the user is only created on first OIDC login. Run the playbook once (realm creation), log in, then run again (ACL assignment).
pveumCLI quirks — multi-word--scopesarguments needansible.builtin.shellwith single-quoted values;--username-claimis only valid onrealm add, notrealm modify.- Naming convention — Authentik was renamed from
turnstiletoguest-relationsto free theturnstilehostname for step-ca. Service-facing DNS was unaffected because it points to Traefik, not the VM. - Authentik blueprints — use
.yamlextension (not.yml), use!Env VAR_NAMEtag (not${VAR_NAME}) for environment variable substitution, mount at/blueprints/custom:roin both server and worker containers. - Redis removed in Authentik 2025.10 — all caching uses PostgreSQL. Remove Redis from compose if upgrading.
- Pre-generated OIDC credentials — client IDs and secrets are pre-generated and stored in vault, shared between the blueprint (provider side) and the consuming service's vault (client side).
- OIDC domain validation in step-ca — the
--domainflag restricts which email domains are accepted. Every Authentik user's email domain must be listed. - SSH principal mapping — certificates contain principals derived from the OIDC email.
AuthorizedPrincipalsFilemaps certificate principals to local users. Without this, certificate auth silently falls through to password auth. - step-ca data directory ownership — container runs as UID 1000. Directories created by Ansible as root cause "permission denied" during init.