When executing the Update Argo CD Application Image Tags step against an Argo CD Application that is deploying a Helm chart, it is necessary to provide extra annotations to define which fields in the Helm values file represent an image to be updated.
This is because an image reference could be made up of multiple different values file entries. Consider the image fields in values.yaml for the Kubernetes agent Helm chart:
...
agent:
image:
repository: octopusdeploy/kubernetes-agent-tentacle
pullPolicy: IfNotPresent
tag: "8.3.3244"
tagSuffix: ""
...
In this case, the agent.image.tag
contains the tag of the image to be updated. However, consider the image fields in this example:
...
global:
image:
registry: docker.io
repositoryAndTag: octopusdeploy/kubernetes-agent-tentacle:8.3.3244
...
In this case, the global.image.repositoryAndTag
contains the tag to be updated.
As the structure of Helm values files can vary widely between charts, it’s necessary to require you to specify custom annotations on the Argo CD Applications.
The annotations are:
Annotation | Alias required | Required | Value description |
---|---|---|---|
argo.octopus.com/image-replace-paths.{alias} | false | true | A comma-delimited Helm-template style string that builds a list of full qualified image names |
argo.octopus.com/image-replace-alias.{alias} | true | false | The path of a ValueFiles entry in the spec.destinations.helm.valuesFiles field |
Details
For Octopus to be able to update the tag of a container image, it must know the fully qualified name, including the registry. An example of a fully qualified name is: docker.io/nginx/nginx:1.29.1
.
This is important so that Octopus doesn’t erroneously update an image from a different registry. For example: images may be set to be sourced from a company-managed registry, where only vetted & tested tags are added.
In this case, we don’t want to update an image that looks like this: my-company-registry.com/nginx/nginx:1.18.1
.
As described above however, the structure of Helm values files can vary significantly. Rather than Octopus guessing (and possibly making a mistake), the onus is on you to specify a Helm-template string that builds a fully qualified name. Octopus can then use this to match on containers being updated and can then use this information to update the specific Helm value that contains the image tag.
Examples
Image path templates
The following is some examples of how to format the Helm-templated string to put into the argo.octopus.com/image-replace-paths.{alias}
annotation based on different values file structures.
Example 1
values.yaml
...
agent:
image:
repository: octopusdeploy/kubernetes-agent-tentacle
pullPolicy: IfNotPresent
tag: "8.3.3244"
tagSuffix: ""
...
annotation value
metadata:
annotations:
argo.octopus.com/image-replace-paths: "docker.io/{{ .Values.agent.image.repository }}:{{ .Values.agent.image.tag }}"
Example 2
values.yaml
...
global:
image:
registry: custom-registry.com
repositoryAndTag: octopusdeploy/kubernetes-agent-tentacle:8.3.3244
pullPolicy: IfNotPresent
tagSuffix: ""
...
annotation value
metadata:
annotations:
argo.octopus.com/image-replace-paths: "{{ .Values.global.image.registry }}/{{ .Values.global.image.repositoryAndTag }}"
Ref sources and alias examples
The following is a list of example Argo CD Application structures, the required annotations and sample values files.
Example 1
With a single values file and a single Helm source, we don’t need the alias
in the paths.
application manifest
...
metadata:
annotations:
argo.octopus.com/project: "proj-1"
argo.octopus.environment: "development"
# When there is a single source, with a single inline file, we use a single annotation to specify call paths
argo.octopus.com/image-replace-paths: "{{ .Values.image.name}}:{{ .Values.image.version}}, {{ .Values.another-image.name }}"
...
spec:
sources:
- repoURL: https://github.com/my-org/my-argo-helm-app
path: "chart"
targetRevision: main
helm:
valueFiles:
- values.yaml
values.yaml
image:
name: nginx/nginx
version: 1.19.0
another-image:
name: busybox:1
Example 2
A single Ref source used to source the values.yaml for the Helm source. In this scenario, the alias
is the same as the ref
value.
...
metadata:
annotations:
argo.octopus.com/project: "proj-1"
argo.octopus.environment: "development"
argo.octopus.com/image-replace-paths.remote-values: "{{ .Values.image.name}}:{{ .Values.image.version}}, {{ .Values.another-image.name }}"
...
spec:
sources:
- repoURL: https://github.com/my-org/my-argo-helm-app
path: "chart"
targetRevision: main
helm:
valueFiles:
- $remote-values/values.yaml
- repoURL: https://github.com/another-repo/values-files-here
targetRevision: main
ref: remote-values
values.yaml
image:
name: nginx/nginx
version: 1.19.0
another-image:
name: busybox:1
Example 3
A Helm source that references both a ref sourced values file and also an in-repo value file.
...
metadata:
annotations:
argo.octopus.com/project: "proj-1"
argo.octopus.environment: "development"
argo.octopus.com/image-replace-alias.core: "app-files/values.yaml"
argo.octopus.com/image-replace-alias.remote: "$remote-values/values.yaml"
argo.octopus.com/image-replace-paths.core: "{{ .Values.image.name}}:{{ .Values.image.version}}"
argo.octopus.com/image-replace-paths.remote: "{{ .Values.different.structure.here.image }}"
...
spec:
sources:
- repoURL: https://github.com/my-org/my-argo-helm-app
path: "chart"
targetRevision: main
helm:
valueFiles:
- app-files/values.yaml
- $remote-values/values.yaml
- repoURL: https://github.com/another-repo/values-files-here
targetRevision: main
ref: remote-values
app-files/values.yaml
image:
name: nginx/nginx
version: 1.19.0
$remote-values/values.yaml
different:
structure:
here:
image: busybox:1
Example 4
A Helm source that references multiple values files from the source repo.
...
metadata:
annotations:
argo.octopus.com/project: "proj-1"
argo.octopus.environment: "development"
# When there are multiple sources, we need an annotation to tell us which file path annotations belong to (Note: the actual name of the alias can be arbitrary)
# if an alias is not provided for a path, that values file will be ignored
argo.octopus.com/image-replace-alias.core: "app-files/values.yaml"
argo.octopus.com/image-replace-alias.overlay: "app-files/values-overlay.yaml"
argo.octopus.com/image-replace-paths.core: "{{ .Values.image.name}}:{{ .Values.image.version}}"
argo.octopus.com/image-replace-paths.overlay: "{{ .Values.different.structure.here.image }}"
...
spec:
sources:
- repoURL: https://github.com/my-org/my-argo-helm-app
path: "chart"
targetRevision: main
helm:
valueFiles:
- app-files/values.yaml
- app-files/values-overlay.yaml
app-files/values.yaml
image:
name: nginx/nginx
version: 1.19.0
$remote-values/values-overlay.yaml
different:
structure:
here:
image: busybox:1
Example 5
A Helm source that has multiple ref sourced values files.
...
metadata:
annotations:
argo.octopus.com/project: "proj-1"
argo.octopus.environment: "development"
argo.octopus.com/image-replace-paths.other-values: "{{ .Values.another-image.name }}"
argo.octopus.com/image-replace-paths.remote-values: "{{ .Values.image.name}}:{{ .Values.image.version}}"
...
spec:
sources:
- repoURL: https://github.com/main-repo/values-files-here
targetRevision: main
ref: other-values
- repoURL: https://github.com/another-repo/values-files-here
targetRevision: main
ref: remote-values
- repoURL: https://github.com/my-repo/my-argo-app
path: "./"
targetRevision: main
helm:
valueFiles:
- $other-values/values.yaml
- $remote-values/values.yaml
$remote-values/values.yaml
image:
name: nginx/nginx
version: 1.19.0
$other-values/values.yaml
another-image:
name: busybox:1
Example 6
Multiple Helm sources with a same values file path in both sources.
Note: The alias requires a fully qualified repo path in the format: {repoUrl}/{targetRevision}/{path}/{valuesFile}
.
...
metadata:
annotations:
argo.octopus.com/project: "proj-1"
argo.octopus.environment: "development"
argo.octopus.com/image-replace-alias.app1: "https://github.com/my-repo/my-argo-app/main/values.yaml"
argo.octopus.com/image-replace-alias.app2: "https://github.com/my-repo/my-other-argo-app/main/cool/values.yaml"
argo.octopus.com/image-replace-paths.app1: "{{ .Values.image.name}}:{{ .Values.image.version}}"
argo.octopus.com/image-replace-paths.app2: "{{ .Values.different.structure.here.image }}"
...
spec:
sources:
- repoURL: https://github.com/my-repo/my-argo-app
path: "./"
targetRevision: main
helm:
valueFiles:
- values.yaml
- repoURL: https://github.com/my-repo/my-other-argo-app
path: "cool"
targetRevision: main
helm:
valueFiles:
- values.yaml
https://github.com/my-repo/my-argo-app/main/values.yaml
image:
name: nginx/nginx
version: 1.19.0
https://github.com/my-repo/my-other-argo-app/main/cool/values.yaml
different:
structure:
here:
image: busybox:1
Example 7
Multiple Helm sources that reference different values files from the same ref source.
...
metadata:
annotations:
argo.octopus.com/project: "proj-1"
argo.octopus.environment: "development"
argo.octopus.com/image-replace-alias.shared1: "$shared-values/some-path/values.yaml"
argo.octopus.com/image-replace-alias.shared2: "$shared-values/another-path/values.yaml"
argo.octopus.com/image-replace-paths.shared1: "{{ .Values.image.name}}:{{ .Values.image.version}}"
argo.octopus.com/image-replace-paths.shared2: "{{ .Values.different.structure.here.image }}"
...
spec:
sources:
- repoURL: https://github.com/another-repo/shared-values-files-here
targetRevision: main
ref: shared-values
- repoURL: https://github.com/my-repo/my-argo-app-be
path: "app-files"
targetRevision: main
helm:
valueFiles:
- $shared-values/some-path/values.yaml
- repoURL: https://github.com/my-repo/my-argo-app-fe
path: "app-files"
targetRevision: main
helm:
valueFiles:
- $shared-values/another-path/values.yaml
$shared-values/some-path/values.yaml
image:
name: nginx/nginx
version: 1.19.0
$shared-values/another-path/values.yaml
different:
structure:
here:
image: busybox:1
Help us continuously improve
Please let us know if you have any feedback about this page.
Page updated on Monday, September 15, 2025