ArgoCD App with Helm from OCI Repo

D
4 min readFeb 9, 2024

--

First, you must create a Secret in the ArgoCD namespace with enableOCI: "true" in your manifest. It was not obvious to me how ArgoCD matches the value of the Secret with the ArgoCD App. Here is an example how to do this with bitnami OCI repo:

apiVersion: v1
kind: Secret
metadata:
labels:
argocd.argoproj.io/secret-type: repository
name: docker-io-helm-oci
namespace: argocd
stringData:
url: registry-1.docker.io/bitnamicharts
name: bitnamicharts
type: helm
enableOCI: "true"

Important items are: metadata.labels, metadata.namespace, stringData.url and stringData.enableOCI.

Let’s take an example with external-dns app. On the documentation page we can see the full chart’s URL path is oci://registry-1.docker.io/bitnamicharts/external-dns.

In the ArgoCD App we are referencing that repo by the registry-1.docker.io/bitnamicharts value of the stringData.url (not the metadata.name) from the Secret, note it goes without the oci protocol prefix, and no chart name at the end.

Also, bitnami is maintaining individual charts for each app so we have to provide path: external-dns separately, and then specify the chart: external-dns again. It might be obvious, but I want to emphasize, the first is the path, and the second is the chart name, it so happens that the texts are the same for both chart & path in this example, though it’s not always the case.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-dns-cf-myorg--com
spec:
destination:
name: ''
namespace: cert-manager
server: 'https://kubernetes.default.svc'

sources:
- repoURL: 'git@github.com:myorg/k8s.git'
path: infra-deployments/external-dns/manifests/bitnami
targetRevision: 'main'

- repoURL: 'registry-1.docker.io/bitnamicharts'
path: 'external-dns'
#helm inspect chart oci://registry-1.docker.io/bitnamicharts/external-dns
targetRevision: 6.36.1
chart: external-dns
helm:
valueFiles:
- $values/infra-deployments/external-dns/cf-myorg--com/bitnami-6.36.1-helm-values.yaml

- repoURL: 'git@github.com:myorg/k8s.git'
targetRevision: HEAD
ref: values

project: prod
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

Important items for the registry-1.docker.io/bitnamicharts are: sources.repoURL, sources.path, sources.targetRevision, and sources.chart

Explaining the App & Secret Manifests

Let's start with obvious: to get the most recent chart version for the sources.targetRevision for the App manifest we just inspect the chart with helm command and look for version: 6.36.1 at the end of the first line or at the very bottom of the output.

helm inspect chart oci://registry-1.docker.io/bitnamicharts/external-dns                                                                                                                                     ─╯
Pulled: registry-1.docker.io/bitnamicharts/external-dns:6.36.1
Digest: sha256:d3c368daadf3a4c0f16a2c640274164419caba4ee58d6512dd3a5f6d4c8f9e3f
annotations:
category: DeveloperTools
images: |
- name: external-dns
image: docker.io/bitnami/external-dns:0.14.0-debian-11-r9
licenses: Apache-2.0
apiVersion: v2
appVersion: 0.14.0
dependencies:
- name: common
repository: oci://registry-1.docker.io/bitnamicharts
tags:
- bitnami-common
version: 2.x.x
description: ExternalDNS is a Kubernetes addon that configures public DNS servers
with information about exposed Kubernetes services to make them discoverable.
home: https://bitnami.com
icon: https://bitnami.com/assets/stacks/external-dns/img/external-dns-stack-220x234.png
keywords:
- external-dns
- network
- dns
maintainers:
- name: VMware, Inc.
url: https://github.com/bitnami/charts
name: external-dns
sources:
- https://github.com/bitnami/charts/tree/main/bitnami/external-dns
version: 6.36.1

Next, split the oci original full chart URL path oci://registry-1.docker.io/bitnamicharts/external-dns into two pieces: we remove the oci protocol prefix and everything afterwords from the URL goes to the first piece till the trailing external-dns part that goes to the second piece, like this:

  1. registry-1.docker.io/bitnamicharts
  2. external-dns

The first piece is used in the Secret stringData.url & App sources.repoURL manifests. Such approach will help us to re-use ArgoCD’s newly created bitnamicharts repo with other bitnami’s apps in the future.

The second piece used in the App manifest for the sources.path variable.

In the helm inspect chart command output we see item name: external-dns meaning the chart’s name is also external-dns, which goes to the sources.chart. Note the same text entered twice to the sources.path and sources.chart in the App manifest. Again, the path & chart are not always the same, though it happens to be the same in this case, watch out when you will be adjusting this how-to for other oci heml charts.

Automate

You can automate this a bit further by placing the Secret manifest in the infra-deployments/external-dns/manifests/bitnami folder in your git so the Secret with the Repository will be created for your ArgoCD with your App in a single step.

This section is app-specific and beyond the point of this article explaining ArgoCD with OCI Helm, but I want to provide a complete and working example: So, don’t forget to add the values file for your helm-chart to the infra-deployments/external-dns/cf-myorg--com/ folder (the folder that goes after the $values/ in the App manifest).

bitnami-6.36.1-helm-values.yaml file example:

#policy: sync
#logLevel: debug
resources:
limits:
cpu: 50m
memory: 50Mi
requests:
memory: 50Mi
cpu: 10m

#tolerations:
# - key: "infra"
# operator: "Equal"
# value: "true"
# effect: "NoSchedule"

provider: cloudflare
zoneIdFilters:
- XXXXXXXXXXXXXXXXX
cloudflare:
proxied: true
#This will look for api-token key in the Secret:
secretName: myorg.com-prod-cf-api-token
domainFilters:
- myorg.com

sources:
#Look only for ingress, change this item if needed
- ingress
#Optional, but useful: process only explicitly included k8s objects such as ingress or service
annotationFilter: "external-dns.alpha.kubernetes.io/filter notin (exclude),external-dns.alpha.kubernetes.io/filter in (include)"

#labelFilter
#ingressClassFilters
extraArgs:
cloudflare-dns-records-per-page: 5000
apiVersion: v1
kind: Secret
metadata:
name: myorg.com-prod-cf-api-token
namespace: cert-manager
data:
api-token: AAAA==
type: Opaque

Note: with the Values file example above, I added optional annotationFilter, so the external-dns will pick up and process an object in your k8s only if you add this annotation explicitly to the object. I found explicit annotation filter more useful instead of external-dns processing everything it could find, giving you more control (and responsibility).

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: <ingressname>
namespace: <namespace>
annotations:
external-dns.alpha.kubernetes.io/filter: include

P.S.

I want to say big thank you and express my gratitude to Merijn Keppel who mentored me and helped to learn and understand GitOps and put me on the right path with k8s and Cilium CNI.

--

--