ArgoCD Apps

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.

Chart values

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

Manifests & templates

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.

Show manifest
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.

Show manifest
{{- 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.

Show manifest
{{- 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)

Show manifest
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)

Show manifest
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
  • Almost every entry runs with autoSync: false. Updates land only after a deliberate sync, diff first.
  • Entries with application: false (external-secrets, reflector) skip the Application template: they are installed by the dedicated manifests in templates/external-helm-repos/.

Trade-offs

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.

← Back to GitOps & Delivery · All service groups