Skip to content

Cross-Alias Tag Confusion When Two Containers Share the Same Image Name #1547

Description

@idvorscak

Component: argocd-image-updater
Version: v1.1.1
Configuration method: CR-based (ImageUpdater custom resource)
Update strategy: digest
Write-back method: git

Summary

When a single ArgoCD application has two containers that use the same image name (registry + repository) but different tags, and both containers are tracked as separate aliases in an ImageUpdater CR, the updater incorrectly reads the current tag/digest of alias A from the container belonging to alias B. This causes:

  1. The wrong tag name to be recorded as the "current" tag for the misidentified alias.
  2. A wrong tag prefix (e.g. latest) to be written into the git write-back override file for the other container.
  3. An infinite commit loop — the updater commits a "change" every reconcile cycle even though the desired state is already in place, because it perpetually reads a stale/wrong digest for one of the aliases.

Environment

  • ArgoCD image-updater: v1.1.1
  • ArgoCD: (standard deployment, argocd namespace)
  • Write-back method: git
  • Helm-based application
  • Two containers in one Deployment, both pulling from the same registry+repository path, under different named tags (tag-a and tag-b)

Reproduction Steps

1. ArgoCD application values (helm)

Two containers in the same deployment, same image repository, different tags:

# values.yaml (simplified)
containers:
  app:
    image:
      name: registry.example.com/myrepo/myimage
      tag: prod
  app-nginx:
    image:
      name: registry.example.com/myrepo/myimage
      tag: nginx-variant

2. ImageUpdater CR

apiVersion: argocd-image-updater.argoproj.io/v1alpha1
kind: ImageUpdater
metadata:
  name: argocd-image-updater
spec:
  writeBackConfig:
    method: git
  applicationRefs:
    - namePattern: "my-app-prod"
      images:
        - alias: app
          imageName: registry.example.com/myrepo/myimage:prod
          commonUpdateSettings:
            updateStrategy: digest
            allowTags: "regexp:^prod$"
          manifestTargets:
            helm:
              name: containers.app.image.name
              tag: containers.app.image.tag
        - alias: app-nginx
          imageName: registry.example.com/myrepo/myimage:nginx-variant
          commonUpdateSettings:
            updateStrategy: digest
            allowTags: "regexp:^nginx-variant$"
          manifestTargets:
            helm:
              name: containers.app-nginx.image.name
              tag: containers.app-nginx.image.tag

3. Trigger

Add the app-nginx alias to the CR for an application where app is already being tracked. On the next reconcile, the bug manifests immediately.

Observed Behaviour

Phase 1 — First reconcile: wrong image_tag read for app alias

The app alias (which tracks tag prod) logs image_tag=nginx-variant, indicating it read the current tag from the app-nginx container rather than its own container. Despite this, both manifestTargets write correctly in the first commit.

Relevant logs (first reconcile, warmup-cache pass):

time="...T05:44:06Z" level=info
  msg="Setting new image to registry.example.com/myrepo/myimage:prod@sha256:<DIGEST-PROD>"
  image_alias=app
  image_name=registry.example.com/myrepo/myimage
  image_tag=nginx-variant        ← WRONG: should be "prod"
  logger=warmup-cache

time="...T05:44:06Z" level=info
  msg="Successfully updated image 'registry.example.com/myrepo/myimage:nginx-variant' to
       'registry.example.com/myrepo/myimage:prod@sha256:<DIGEST-PROD>', but pending spec update"
  image_alias=app
  image_tag=nginx-variant        ← WRONG source tag reported
  logger=warmup-cache

The commit message generated for this first commit is also wrong — it reports both aliases as updating from nginx-variant:

build: automatic update of my-app-prod

updates image myrepo/myimage tag 'nginx-variant' to 'sha256:<DIGEST-PROD>'
updates image myrepo/myimage tag 'nginx-variant' to 'sha256:<DIGEST-NGINX>'

Both lines reference nginx-variant as the source tag. The first line should have referenced prod as the source tag for the app alias.

Phase 2 — Subsequent reconciles: infinite commit loop (2 parameter updates per cycle)

Every ~2 minutes, image-updater commits 2 parameter updates for the application, even though the desired state is already written correctly. The app alias continues reading image_tag=nginx-variant:

time="...T05:46:15Z" level=info
  msg="Setting new image to registry.example.com/myrepo/myimage:prod@sha256:<DIGEST-PROD>"
  image_alias=app
  image_tag=nginx-variant        ← still wrong every cycle
  logger=reconcile

time="...T05:46:16Z" level=info
  msg="Setting new image to registry.example.com/myrepo/myimage:nginx-variant@sha256:<DIGEST-NGINX>"
  image_alias=app-nginx
  image_tag=nginx-variant        ← correct
  logger=reconcile

msg="Committing 2 parameter update(s) for application my-app-prod"

This repeats at 05:44, 05:46, 05:48 — every reconcile cycle.

Phase 3 — After ArgoCD sync: tag prefix corruption and 1-parameter loop

After ArgoCD syncs the application and the containers start running with their pinned digests, the running image in the ArgoCD application spec is stored as a bare digest (no tag name prefix). At this point the app alias shifts to reading by digest:

time="...T05:50:27Z" level=info
  msg="Setting new image to registry.example.com/myrepo/myimage:prod@sha256:<DIGEST-PROD>"
  image_alias=app
  image_digest="sha256:<DIGEST-NGINX>"    ← reading nginx-variant's digest as app's current digest
  logger=reconcile

time="...T05:50:27Z" level=info
  msg="Successfully updated image 'registry.example.com/myrepo/myimage@sha256:<DIGEST-NGINX>'
       to 'registry.example.com/myrepo/myimage:prod@sha256:<DIGEST-PROD>'"
  image_alias=app
  image_digest="sha256:<DIGEST-NGINX>"

The updater now commits 1 parameter update per cycle (down from 2). The single parameter changed is containers.app-nginx.image.tag — it is corrupted from:

nginx-variant@sha256:<DIGEST-NGINX>

to:

latest@sha256:<DIGEST-NGINX>

The tag prefix nginx-variant is replaced with latest. The digest itself does not change. This is data corruption: latest may point to a completely different image layer in the registry, causing the wrong binary to be deployed if the override is ever re-applied without a running container pinning the digest.

The infinite loop then continues: every reconcile, containers.app-nginx.image.tag is rewritten from nginx-variant@sha256:<DIGEST-NGINX> to latest@sha256:<DIGEST-NGINX>, generating 21+ spurious git commits over the observed log window.

Git Write-Back: Before and After

Override file before corruption (correct state):

helm:
  parameters:
  - name: containers.app-nginx.image.name
    value: registry.example.com/myrepo/myimage
    forcestring: true
  - name: containers.app-nginx.image.tag
    value: nginx-variant@sha256:<DIGEST-NGINX>    # correct
    forcestring: true
  - name: containers.app.image.name
    value: registry.example.com/myrepo/myimage
    forcestring: true
  - name: containers.app.image.tag
    value: prod@sha256:<DIGEST-PROD>              # correct
    forcestring: true

Override file after corruption (commit introduced by image-updater):

  - name: containers.app-nginx.image.tag
    value: latest@sha256:<DIGEST-NGINX>           # WRONG prefix: 'nginx-variant' → 'latest'

Root Cause (Hypothesis)

During the read phase, when image-updater resolves the current image for each alias from the ArgoCD application spec, it appears to match containers by image name only (registry + repository path), without considering the tag. When two containers share the same image name, both are matched by both aliases — the first (or last) matched container's tag is then attributed to whichever alias happens to process it, causing the cross-alias confusion.

The write phase (using manifestTargets) is respected and writes to the correct helm parameters, but because the read phase fed the wrong current digest into the alias's state, the updater perpetually believes an update is needed and also corrupts the tag prefix of the sibling alias's override parameter.

Expected Behaviour

When two aliases in the same application reference the same image name but different tags, and each alias has explicit manifestTargets pointing to distinct helm parameters, the updater should:

  1. Read the current image state for each alias from its own manifestTargets helm parameter (rather than scanning all containers by image name).
  2. Correctly attribute image_tag=prod to the app alias and image_tag=nginx-variant to the app-nginx alias.
  3. Stop committing after the write-back override already reflects the desired state.
  4. Never write a tag prefix (latest) that was not configured in allowTags for that alias.

Workaround

Remove one of the conflicting aliases from the ImageUpdater CR so that only one alias tracks the shared image name. The removed alias must be managed with manual digest pins in the git override file.

Additional Notes

  • The allowTags filter is set correctly for both aliases (regexp:^prod$ and regexp:^nginx-variant$). The bug occurs before allowTags filtering — in the current-state read phase.
  • The bug is reproducible deterministically: it triggers immediately on the first reconcile after adding a second alias whose image name matches an existing alias.
  • The problem does not occur when the two containers use images from different repositories.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions