5.6 KiB
Homelab GitOps
Argo CD is the only component that deploys the resources in this repository. Application CI publishes immutable container images and then commits the selected image tag here; it never receives Kubernetes credentials.
Repository layout
apps/immich-sync/is the desired Kubernetes state for Immich Share Sync.apps/immichframe/wraps the reusable ImmichFrame chart with this cluster's hostname, album selection, resources, and encrypted API credential.bootstrap/contains Argo CD projects and Applications, including the upstream Sealed Secrets controller.scripts/update-immich-sync-image.shis the single image-promotion interface used by CI.scripts/update-immichframe-chart.shis the chart-promotion interface used by ImmichFrame chart CI.
The only canonical Immich Share Sync image version is images[0].newTag in
apps/immich-sync/kustomization.yaml. The resource manifest deliberately omits a tag, and Kustomize supplies the
immutable full Git SHA while rendering.
The only canonical ImmichFrame chart version is the immichframe dependency
in apps/immichframe/Chart.yaml. Chart CI publishes a unique immutable version
and commits that dependency update here. The chart owns the reusable workload
shape; apps/immichframe/values.yaml owns only this cluster's choices.
First-time setup
The cluster already runs Argo CD in the argocd namespace and exposes its UI at https://cd.brunner.ninja.
-
Push this repository so Argo CD can read its public HTTPS URL.
-
Configure the
immich-syncrepository's cross-repository token as described below. -
Review the rendered workload:
kubectl kustomize apps/immich-sync -
Record the existing persistent volume before the first synchronization:
kubectl --namespace default get pvc immich-sync-stats \ -o jsonpath='{.metadata.uid}{"|"}{.spec.volumeName}{"\n"}' -
Bootstrap the Argo CD resources:
./bootstrap/install.sh -
Inspect the result in the UI or CLI:
argocd app get immich-sync argocd app diff immich-sync kubectl --namespace default rollout status deployment/immich-sync kubectl --namespace default get deployment immich-sync \ -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' -
Confirm that the PVC UID and volume name are unchanged. Only after Argo CD reports the application
SyncedandHealthy, remove the obsolete CI deployer and delete the oldKUBE_CONFIG_BASE64secret from Gitea:kubectl --namespace default delete rolebinding/immich-sync-deployer \ role/immich-sync-deployer serviceaccount/immich-sync-deployer \ secret/immich-sync-deployer-token
The bootstrap Application enables automatic synchronization, pruning, and self-healing. Consequently, direct edits
to its managed Kubernetes resources are reverted to Git. Change Git when experimenting, observe OutOfSync, and then
watch Argo CD return the application to Synced and Healthy.
Give Immich Share Sync CI permission to promote images
The built-in Gitea Actions job token cannot write to another repository. Create a dedicated bot account, for example
gitops-promoter, add it to feedc0de/homelab-gitops with repository Write permission, and create a personal
access token for that account with only the write:repository scope.
In feedc0de/immich-sync, open Settings → Actions → Secrets and create GITOPS_TOKEN containing that token. The
workflow passes it directly to actions/checkout while checking out this repository; the checkout action configures
authenticated HTTPS for the final git push without placing the token in a committed URL.
The main-branch workflow now performs this sequence:
- Build and push
registry.brunner.ninja/feedc0de/immich-sync:<source SHA>. - Verify that the completed build still belongs to the current
maincommit. - Update
apps/immich-sync/kustomization.yamlwith that SHA. - Commit and push the promotion to this repository.
- Let Argo CD detect and synchronize the Git commit.
For a personal token owned by feedc0de, write:repository also applies to other repositories that account can
write. A dedicated bot restricted to this one repository therefore gives the token a meaningfully smaller blast
radius.
Manual recovery path
Argo CD is the normal deployment mechanism. If it is unavailable, apps/immich-sync/install.sh renders, performs a
server-side dry-run, applies the same Kustomize source, and waits for the Deployment rollout. A later Argo CD refresh
will see the same desired resources.
Sealed Secrets
Argo CD installs the official Sealed Secrets chart pinned in
bootstrap/sealed-secrets.yaml. Public Git stores only SealedSecret
ciphertext. The controller decrypts that ciphertext into namespace-scoped
Kubernetes Secrets; the private sealing key never belongs in this repository.
Install the matching kubeseal client, then seal a Secret for its final name
and namespace. The default strict scope prevents ciphertext from being moved to
another name or namespace:
kubectl --namespace default create secret generic example \
--from-literal=token='replace-me' \
--dry-run=client --output=yaml \
| kubeseal \
--controller-name sealed-secrets-controller \
--controller-namespace sealed-secrets \
--format yaml > example-sealed-secret.yaml
The controller's generated private key is the disaster-recovery key for every
sealed value. Back up the Secrets labelled
sealedsecrets.bitnami.com/sealed-secrets-key=active from namespace
sealed-secrets to an encrypted offline store. Do not commit that backup. A
cluster rebuilt without one of those keys cannot decrypt existing ciphertext.