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.sh is the single image-promotion interface used by CI.
  • scripts/update-immichframe-chart.sh is 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.

  1. Push this repository so Argo CD can read its public HTTPS URL.

  2. Configure the immich-sync repository's cross-repository token as described below.

  3. Review the rendered workload:

    kubectl kustomize apps/immich-sync
    
  4. Record the existing persistent volume before the first synchronization:

    kubectl --namespace default get pvc immich-sync-stats \
      -o jsonpath='{.metadata.uid}{"|"}{.spec.volumeName}{"\n"}'
    
  5. Bootstrap the Argo CD resources:

    ./bootstrap/install.sh
    
  6. 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"}'
    
  7. Confirm that the PVC UID and volume name are unchanged. Only after Argo CD reports the application Synced and Healthy, remove the obsolete CI deployer and delete the old KUBE_CONFIG_BASE64 secret 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:

  1. Build and push registry.brunner.ninja/feedc0de/immich-sync:<source SHA>.
  2. Verify that the completed build still belongs to the current main commit.
  3. Update apps/immich-sync/kustomization.yaml with that SHA.
  4. Commit and push the promotion to this repository.
  5. 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.

S
Description
No description provided
Readme
418 KiB
Languages
Shell 100%