authentik
Kubernetes deployment source-of-truth repository for authentik.
This template is intended for applications deployed with ArgoCD through an
ApplicationSet. It uses a base/ plus overlays/ layout, stores runtime
secrets as SOPS-encrypted manifests, and includes a Gitea workflow that checks
Kubernetes YAML syntax on every push and pull request. It also ships with a
dormant bootstrap ApplicationSet that only becomes active after an explicit
enable change and a promotion into main.
Layout
.
├── .gitea/
│ ├── template
│ └── workflows/validate.yaml
├── .github/
│ └── workflows/check-sops-sync.yml
├── .sops.yaml
├── bootstrap/
│ ├── applicationset.yaml
│ └── config.yaml
├── manifest/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── kustomization.yaml
│ │ ├── namespace.yaml
│ │ └── service.yaml
│ ├── components/
│ │ └── example-component/
│ └── overlays/
│ └── production/
│ ├── ingressroute.yaml
│ ├── kustomization.yaml
│ ├── secret-generator.yaml
│ ├── secret.secret.yaml
│ └── storage/
│ ├── persistentvolumeclaim.yaml
│ └── persistentvolume-nfs.yaml
└── scripts/
├── ci/check-sops-sync
├── git-hooks/pre-commit
├── install-git-hooks
└── lib/
├── sops-path-regexes
└── sops-sync-lib
First edits
Update these files before the first deployment:
manifest/base/deployment.yamlSet the container image, ports, probes, resource requests, and any required environment variables.manifest/overlays/production/ingressroute.yamlSet the real hostname, entry point names, and middleware references if used.manifest/overlays/production/storage/persistentvolumeclaim.yamlSet the requested size, access mode, and storage class for the environment..sops.yamlReplace the sample age recipient with the public key that ArgoCD should use for decryption.manifest/overlays/production/secret.secret.yamlRe-encrypt the placeholder secret values with your own data.
Bootstrap activation model
This template is designed to start on an init branch. The intended flow is:
- Create the repository from this template with
initas the initial branch. - Tailor the manifests, secrets, storage, and ingress on
init. - Keep
bootstrap/config.yamlset toenabled: falsewhile the repository is being prepared. - Create
mainby merginginitonly when the repository is ready to be part of the delivery pipeline. - Deliberately change
bootstrap/config.yamltoenabled: trueonmainto allow the workflow to apply the bootstrapApplicationSet.
The workflow behavior is:
bootstrap/applicationset.yamlis ignored by validation while bootstrap is disabled- no cluster apply is attempted unless the workflow runs on
main - cluster apply also requires the
KUBECONFIG_B64Gitea secret
Bootstrap files
bootstrap/config.yamlRepository-local activation switch. Default is disabled.bootstrap/applicationset.yamlArgoCDApplicationSetmanifest template for this repository.
If the repository is created by copying files with plain Git instead of Gitea's
template expansion, you must replace the ${REPO_NAME...} placeholders in
bootstrap/applicationset.yaml before enabling bootstrap.
Storage model
Storage is overlay-owned, so each environment can request different backing storage without sharing claims across environments.
manifest/overlays/production/storage/persistentvolumeclaim.yamlDefault writable application storage. With the defaultlonghornstorageClassName, Kubernetes dynamically provisions the backing PV when the claim is created.manifest/overlays/production/storage/persistentvolume-nfs.yamlOptional static PV example for NFS-backed or pre-provisioned shared storage. This file is not referenced by default. Add it to the overlay kustomization only when you need a static PV.
The base deployment mounts the claim at /data.
If storage should be shared between multiple applications in different repositories, keep those applications in the same namespace and have a single repository own the PVC resource. The other repositories should reference the same claim name from their deployments instead of creating a duplicate PVC.
Secret workflow
- Generate an age key pair.
- Store the private key where ArgoCD / ksops can read it.
- Update
.sops.yamlwith the matching public recipient. - Re-encrypt
manifest/overlays/production/secret.secret.yaml.
Example:
age-keygen -o age-key.txt
sops --encrypt --in-place manifest/overlays/production/secret.secret.yaml
Repo-managed SOPS sync
This repo also supports sync-managed secrets using the naming convention:
foo.yaml -> foo.enc.yaml -> foo.enc.yaml.sync-hmac
foo.yamlLocal plaintext source. Keep it gitignored and untracked.foo.enc.yamlTracked SOPS-encrypted file.foo.enc.yaml.sync-hmacTracked HMAC sidecar used to prove the encrypted file still matches the last synced plaintext content.
Requirements:
SOPS_SYNC_HMAC_KEYmust be set locally for hooks and in CIsops,openssl, and eitheryqorpython3withPyYAMLmust be available- CI also needs access to the repo's SOPS decryption mechanism, such as an age key
Generate a local HMAC key:
openssl rand -hex 32
Set it for the current shell session:
export SOPS_SYNC_HMAC_KEY='<paste-generated-key-here>'
Persist it in zsh:
echo "export SOPS_SYNC_HMAC_KEY='<paste-generated-key-here>'" >> ~/.zshrc
source ~/.zshrc
Persist it in bash:
echo "export SOPS_SYNC_HMAC_KEY='<paste-generated-key-here>'" >> ~/.bashrc
source ~/.bashrc
Verify it is set:
printenv SOPS_SYNC_HMAC_KEY
Install the local hook:
scripts/install-git-hooks
Local workflow:
- Keep your plaintext secret local-only, for example
manifest/overlays/production/secret.yaml. - Stage either the plaintext file or its
.enc.yamltwin. - On pre-commit, the hook encrypts the plaintext if needed, updates the
.sync-hmacsidecar, and stages both tracked files.
CI behavior:
- CI fails if a tracked
.enc.yamlfile is not valid SOPS content - CI fails if the
.sync-hmacsidecar is missing - CI fails if decrypted plaintext no longer matches the tracked sidecar HMAC
Security tradeoffs:
- The sidecar is an HMAC, not a plain checksum, so the plaintext fingerprint is
keyed and not directly reusable without
SOPS_SYNC_HMAC_KEY - Anyone who can decrypt the repo secret files and also access the HMAC key can recompute sidecars, so protect both inputs appropriately
- Plaintext local files must remain gitignored and never be committed
ArgoCD ApplicationSet path
Point the generated ArgoCD Application at:
manifest/overlays/production
If your ApplicationSet uses repository scanning, this repository is designed
to be the single source of truth for one application, with ArgoCD rendering the
selected overlay.