Decision. App-of-apps: one values.yaml lists every service
Alternative. Individual Application manifests scattered across repos
Why. Adding a service is one dictionary entry, and the full catalog is reviewable in a single file.
The catalog: one chart that registers every other service with ArgoCD
The argocd-apps chart is how everything enters the cluster. Its values.yaml lists every Application in the catalog, and its templates render the Application resources grouped into three AppProjects (k3s-services, cluster-services, applications). Bootstrap order matters: Vault is installed by CI first and ArgoCD second; this chart then registers everything else, while ESO hands ArgoCD the repository credentials it needs to pull each service's chart. This page covers the catalog chart itself; the ArgoCD controller has its own page.
The full argocd-apps/values.yaml from the service's
own chart:
argoConfig:
apps:
external-secrets:
application: false
project: k3s-services
info:
"This service is installed with the external-secrets-operator.yaml file in
the external-hel-repos folder"
autoSync: false
reflector:
application: false
project: k3s-services
info: "This service is installed with the reflector.yaml file in the
external-hel-repos folder"
autoSync: false
longhorn:
application: true
project: k3s-services
autoSync: false
vault:
application: true
project: k3s-services
autoSync: false
# Bootstrapped via GitLab CI before ArgoCD exists (ArgoCD reads its
# own secrets from Vault via ESO). ArgoCD adopts the existing release
# on first sync — no prune so it doesn't delete anything CI created.
cert-manager:
application: true
project: k3s-services
autoSync: false
metallb:
application: true
project: k3s-services
autoSync: false
istio:
# true: replaced the traefik entry 2026-09-20 (Traefik decommissioned)
application: true
project: k3s-services
autoSync: false
velero:
application: true
project: k3s-services
autoSync: false
monitoring:
application: true
project: cluster-services
autoSync: false
rustfs:
application: true
project: cluster-services
autoSync: false
zot:
application: true
project: cluster-services
autoSync: false
postgres:
application: true
project: cluster-services
info: "This application includes cloudnative-pg operator, pg-admin and pg-bouncer"
autoSync: false
argo-events:
application: true
project: cluster-services
autoSync: false
argo-workflows:
application: true
project: cluster-services
autoSync: false
argo-rollouts:
application: true
project: cluster-services
autoSync: false
homepage:
application: true
project: cluster-services
autoSync: true
media-systems:
application: true
project: cluster-services
autoSync: false
metabase:
application: true
project: cluster-services
autoSync: false
automq:
application: true
project: cluster-services
autoSync: false
qdrant:
application: true
project: cluster-services
autoSync: true
##################
## Applications ##
##################
bouncer:
application: true
project: applications
autoSync: false
cloudflared:
application: true
project: applications
autoSync: true
open-webui:
application: true
project: cluster-services
autoSync: false
openvino:
application: true
project: applications
autoSync: false
trove:
application: true
project: applications
autoSync: false
whatsapp-bot:
application: true
project: applications
autoSync: false
wos-assistant:
application: true
project: applications
autoSync: false
quote-my-shizzle:
application: true
project: applications
autoSync: false
meridian:
application: true
project: applications
autoSync: false
whistle:
application: true
project: applications
autoSync: false
muse:
application: true
project: applications
autoSync: false
beacon:
application: true
project: applications
autoSync: false
projects:
- k3s-services
- cluster-services
- applications argocd-apps/templates/applications/gitlab-apps-template.yaml The app-of-apps template: renders one ArgoCD Application per argoConfig.apps entry. This is how every service in this catalog reaches the cluster.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: {{ .Release.Name }}-autosync
namespace: {{ .Release.Namespace }}
spec:
generators:
- list:
elements:
{{- range $app, $property := .Values.argoConfig.apps }}
{{- if and $property.application $property.autoSync }}
- name: {{ $app | lower }}
repoURL: "https://gitlab.com/opi5-cluster/{{ $app | lower }}.git"
project: {{ $property.project | lower }}
autoSync: "true"
{{- end }}
{{- end }}
template:
metadata:
name: "{{ `{{ name }}` }}"
namespace: "{{ `{{ name }}` }}"
spec:
project: "{{ `{{ project }}` }}"
source:
helm:
releaseName: "{{ `{{ name }}` }}"
valueFiles:
- values.yaml
repoURL: "{{ `{{ repoURL }}` }}"
path: .
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: "{{ `{{ name }}` }}"
syncPolicy:
automated:
prune: false
selfHeal: false
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
- PrunePropagationPolicy=foreground
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: {{ .Release.Name }}-no-autosync
namespace: {{ .Release.Namespace }}
spec:
generators:
- list:
elements:
{{- range $app, $property := .Values.argoConfig.apps }}
{{- if and $property.application (not $property.autoSync) }}
- name: {{ $app | lower }}
repoURL: "https://gitlab.com/opi5-cluster/{{ $app | lower }}.git"
project: {{ $property.project | lower }}
autoSync: "false"
{{- end }}
{{- end }}
template:
metadata:
name: "{{ `{{ name }}` }}"
namespace: "{{ `{{ name }}` }}"
spec:
project: "{{ `{{ project }}` }}"
source:
helm:
releaseName: "{{ `{{ name }}` }}"
valueFiles:
- values.yaml
repoURL: "{{ `{{ repoURL }}` }}"
path: .
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: "{{ `{{ name }}` }}"
syncPolicy:
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
- PrunePropagationPolicy=foreground argocd-apps/templates/projects/projects-template.yaml Renders the three ArgoCD projects (k3s-services, cluster-services, applications) that scope every Application.
{{- range $.Values.argoConfig.projects -}}
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: {{ . | lower }}
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-10"
spec:
clusterResourceWhitelist:
- group: '*'
kind: '*'
destinations:
- name: '*'
namespace: '*'
server: https://kubernetes.default.svc
namespaceResourceWhitelist:
- group: '*'
kind: '*'
sourceRepos:
- '*'
---
{{- end -}} argocd-apps/templates/repo-secrets/helm-repo-secret-template.yaml Renders the Helm repository credentials ArgoCD needs to pull the upstream charts.
{{- range $app, $property := .Values.argoConfig.apps -}}
{{- if $property.application -}}
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: "repo-{{ $app | lower }}-external-secret"
namespace: argocd
spec:
refreshInterval: 24h
secretStoreRef:
kind: ClusterSecretStore
name: vault-cluster-secret-store
target:
name: "repo-{{ $app | lower }}-secret"
creationPolicy: Owner
template:
engineVersion: v2
metadata:
labels:
argocd.argoproj.io/secret-type: repository
data:
url: "https://gitlab.com/opi5-cluster/{{ $app | lower }}.git"
name: {{ $app | lower }}
username: "{{ `{{ .ci_user }}` }}"
password: "{{ `{{ .ci_password }}` }}"
insecure: "false"
forceHttpBasicAuth: "false"
enableLfs: "false"
type: git
dataFrom:
- extract:
key: gitlab
---
{{- end -}}
{{- end -}} argocd-apps/templates/external-helm-repos/external-secrets-operator.yaml Standalone manifest: installs External Secrets Operator from its upstream chart (application: false)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-secrets-operator
namespace: argocd
annotations:
meta.helm.sh/release-name: argocd-configuration
meta.helm.sh/release-namespace: argocd
argocd.argoproj.io/sync-wave: "-9"
labels:
app.kubernetes.io/managed-by: Helm
meta.helm.sh/release-name: argocd-configuration
meta.helm.sh/release-namespace: argocd
spec:
destination:
namespace: external-secrets-operator
server: https://kubernetes.default.svc
project: k3s-services
source:
chart: external-secrets
helm:
parameters:
- name: serviceAccount.name
value: external-secrets-service-account
repoURL: https://charts.external-secrets.io
targetRevision: 2.9.0
syncPolicy:
managedNamespaceMetadata:
labels:
goldilocks.fairwinds.com/enabled: "true"
goldilocks.fairwinds.com/vpa-update-mode: "auto"
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
- PrunePropagationPolicy=foreground argocd-apps/templates/external-helm-repos/reflector.yaml Standalone manifest: installs Reflector from its upstream chart (application: false)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: reflector
namespace: argocd
annotations:
meta.helm.sh/release-name: argocd-configuration
meta.helm.sh/release-namespace: argocd
argocd.argoproj.io/sync-wave: "-6"
labels:
app.kubernetes.io/managed-by: Helm
meta.helm.sh/release-name: argocd-configuration
meta.helm.sh/release-namespace: argocd
spec:
destination:
namespace: reflector
server: https://kubernetes.default.svc
project: k3s-services
source:
chart: reflector
helm:
parameters:
- name: serviceAccount.name
value: reflector-service-account
- name: resources.requests.cpu
value: 15m
- name: resources.requests.memory
value: 110M
repoURL: https://emberstack.github.io/helm-charts
targetRevision: 10.0.65
syncPolicy:
managedNamespaceMetadata:
labels:
goldilocks.fairwinds.com/enabled: "true"
goldilocks.fairwinds.com/vpa-update-mode: "auto"
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
- PrunePropagationPolicy=foreground Decision. App-of-apps: one values.yaml lists every service
Alternative. Individual Application manifests scattered across repos
Why. Adding a service is one dictionary entry, and the full catalog is reviewable in a single file.
Decision. autoSync false as the default posture
Alternative. Full auto-sync everywhere
Why. Upgrades are deliberate events on a small cluster; the few auto-synced entries (homepage, qdrant, cloudflared) are the low-risk, self-healing ones.
Decision. Three AppProjects as a blast-radius boundary
Alternative. A single default project
Why. Platform, cluster services, and user applications are edited, synced, and fail independently.