This is a GitOps infrastructure repository for a single-node K3s cluster. ArgoCD manages all applications declaratively from this repo. The stack includes Vault, Authentik, CloudNativePG, cert-manager, and External Secrets Operator.
- Git is the single source of truth -- all changes MUST go through git commits
- All K8s resources must be declarative (no imperative kubectl commands in production)
- ArgoCD auto-syncs with pruning and self-healing enabled
- Never manually apply manifests (except bootstrap)
- NEVER hardcode secrets in YAML or HCL files
- All secrets MUST be stored in Vault and synced via ExternalSecret CRs
- Use OIDC/ForwardAuth authentication via Authentik for all services
- TLS certificates MUST be managed by cert-manager with Let's Encrypt
- All ingress endpoints MUST use HTTPS (TLS termination at Traefik)
- Use Kustomize for all K8s configurations (no Helm charts unless it is the recommanded way to install something in the official doc)
- Follow the existing directory structure:
k8s/apps/<service>/ - Each service gets its own directory with: kustomization.yaml, deployment.yaml, service.yaml, ingress.yaml, etc.
- Use consistent naming:
<service>-<resource-type>(e.g.,keycloak-deployment,vault-ingress) - Always specify namespaces explicitly in resources
- Use ArgoCD Application CRs in
k8s/apps/argocd/templates/for new services
- Separate Terraform configs by service:
terraform/vault/,terraform/authentik/ - Use explicit provider versions
- Store statefile locally (this is a homelab, not production)
- Always run
terraform planbeforeterraform apply - Document manual steps in README.md if Terraform can't handle them
- All external services need an IngressRoute in their app directory
- Use consistent hostname pattern:
<service>.armleth.fr - TLS must reference a cert-manager Certificate
- Example structure:
apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: <service> namespace: <namespace> spec: entryPoints: [websecure] routes: - match: Host(`<service>.armleth.fr`) kind: Rule services: - name: <service> port: <port> tls: secretName: <service>-tls-secret
- All TLS certs defined in
k8s/apps/cert-manager-config/certificates/<service>.yaml - Use
letsencrypt-prodClusterIssuer - Certificate must be in the same namespace as the IngressRoute
- Add to
k8s/apps/cert-manager-config/kustomization.yamlresources list
- Define ExternalSecret in the service's namespace
- Reference the
vault-backendClusterSecretStore - Path format:
secret/data/<service>/<key> - Mount as Kubernetes Secret with consistent naming:
<service>-<purpose>-secret
Any service whose IngressRoute references the authentik-forward-auth middleware, or that uses Authentik OIDC, MUST also be declared in Terraform — otherwise Authentik returns Not Found on the outpost path and the service is unreachable.
Two files matter, both under terraform/authentik/:
- ForwardAuth services →
proxy-providers.tf:- Add
authentik_provider_proxy.<service>withexternal_host = "https://<host>.${local.domain}"andmode = "forward_single". - Add
authentik_application.<service>pointing at that provider. - Add
authentik_policy_binding.<service>_<group>for every group that should have access (mirror the closest sibling service). - Append
authentik_provider_proxy.<service>.idto theauthentik_outpost.embedded.protocol_providerslist at the bottom of the file. The embedded outpost does NOT auto-attach — a provider that isn't in this list is invisible to Traefik's ForwardAuth call.
- Add
- OIDC services →
oidc-providers.tf: addauthentik_provider_oauth2+authentik_application+ bindings in the same pattern.
After editing, run terraform plan / terraform apply from terraform/authentik/ (see "Modifying Terraform Configs" below). This step is easy to forget because ArgoCD syncs the K8s side automatically but Terraform is manual.
- Update README.md when adding new services or changing bootstrap steps
- Update setup.sh when adding new services or changing bootstrap steps
- README.md and setup.sh MUST be kept in sync with every new implementation
- Include kubectl wait commands for readiness checks
- Document port-forward commands for local access
- Explain manual steps that can't be automated
- Read the existing manifest files BEFORE making changes
- Maintain existing patterns and naming conventions
- Don't refactor code unnecessarily -- only change what's needed
- Test changes with
kubectl diff -korkubectl apply --dry-run=server - For Terraform: always run
terraform planto preview changes
- Write clear, descriptive commit messages
- Use conventional commits format when possible:
feat:,fix:,docs:,refactor: - Don't commit sensitive files: vault-init.json, terraform.tfstate, kubeconfig
- Create directory:
k8s/apps/<service>/ - Create manifests: deployment.yaml, service.yaml, ingress.yaml, kustomization.yaml
- If secrets needed: create ExternalSecret, ensure path exists in Vault
- Create certificate:
k8s/apps/cert-manager-config/certificates/<service>.yaml - Create ArgoCD Application:
k8s/apps/argocd/templates/<service>.yaml(skip if the service lives in a namespace whose ArgoCD Application already watches the parent directory — e.g.media/) - If the IngressRoute uses
authentik-forward-author the service uses OIDC: add the provider + application + policy bindings interraform/authentik/proxy-providers.tf(oroidc-providers.tf), register the provider inauthentik_outpost.embedded.protocol_providers, thenterraform plan/apply. See "Authentik Integration" above. This step is NEVER optional for protected services — skipping it gives a "Not Found" page from Authentik. - Update kustomization.yaml files as needed (parent
media/,cert-manager-config/, etc.) - Update the homepage configuration in
k8s/apps/homepage/configmap.yamlto add the new service entry in the appropriate category - Commit and push -- ArgoCD will sync the K8s side automatically (Authentik in step 6 is NOT synced by ArgoCD)
- Port-forward to Vault:
kubectl port-forward -n vault svc/vault 8200:8200 - Set env vars:
export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=<token> - Store secret:
vault kv put secret/<service> <key>=<value> - Create ExternalSecret CR in K8s manifest
- Port-forward to the service if needed (Vault or Authentik)
- Set required environment variables
- Run
terraform planto preview - Run
terraform applyto execute - Update README.md if manual steps changed
- Don't use Helm unless this is the recommended way to install it in the official documentation, or of it is too complex to do it otherwise.
- Don't create generic utilities or over-engineer solutions
- Don't add unnecessary error handling for impossible scenarios
- Don't refactor working code just to "improve" it
- Don't add comments explaining obvious YAML structure
- Don't create new documentation files without explicit request
- Don't use kubectl apply manually (except for bootstrap)
- Don't store secrets in git or pass them as arguments in manifests