Skip to content

Docker: Mirror images to GitHub Container Registry#3098

Open
VietND96 wants to merge 4 commits intotrunkfrom
deploy-ghcr
Open

Docker: Mirror images to GitHub Container Registry#3098
VietND96 wants to merge 4 commits intotrunkfrom
deploy-ghcr

Conversation

@VietND96
Copy link
Member

Thanks for contributing to the Docker-Selenium project!
A PR well described will help maintainers to quickly review and merge it

Before submitting your PR, please check our contributing guidelines, applied for this repository.
Avoid large PRs, help reviewers by making them as simple and short as possible.

Description

Fixes #2939

Motivation and Context

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

Signed-off-by: Viet Nguyen Duc <nguyenducviet4496@gmail.com>
@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Mirror Docker images to GitHub Container Registry

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add GitHub Container Registry (GHCR) login to deployment workflows
• Mirror Docker images from Docker Hub to GHCR across all release pipelines
• Implement new Makefile targets for GHCR image mirroring operations
• Support versioned, latest, nightly, and browser-specific image mirroring
Diagram
flowchart LR
  A["Docker Hub Images"] -->|"docker buildx imagetools create"| B["GHCR Images"]
  C["Deploy Workflow"] -->|"Login GHCR"| D["Mirror versioned/latest/nightly"]
  E["Browser Release Workflows"] -->|"Login GHCR"| F["Mirror browser images"]
  D --> B
  F --> B
Loading

Grey Divider

File Changes

1. .github/workflows/deploy.yml ✨ Enhancement +27/-0

Add GHCR mirroring to deploy workflow

• Add GHCR login step using SELENIUM_CI_TOKEN secret
• Add three new mirror tasks for versioned, latest, and browser images to GHCR
• Each mirror task uses retry logic with configurable timeouts and attempts
• Mirror tasks invoke new Makefile targets release_ghcr, release_ghcr_latest, and
 tag_and_push_browser_images_ghcr

.github/workflows/deploy.yml


2. .github/workflows/nightly.yml ✨ Enhancement +9/-0

Add GHCR nightly image mirroring

• Add GHCR login step using SELENIUM_CI_TOKEN secret
• Add mirror nightly images task that calls release_ghcr_nightly Makefile target
• Mirror task includes retry configuration with 20-minute timeout

.github/workflows/nightly.yml


3. .github/workflows/release-chrome-for-testing-versions.yml ✨ Enhancement +6/-0

Add GHCR mirroring for Chrome for Testing

• Add conditional GHCR login step when PUSH_IMAGE is true
• Add mirror images task that calls mirror_browser_images_ghcr Makefile target
• Pass GHCR_NAMESPACE and BROWSER_NAME environment variables to mirror task

.github/workflows/release-chrome-for-testing-versions.yml


View more (4)
4. .github/workflows/release-chrome-versions.yml ✨ Enhancement +6/-0

Add GHCR mirroring for Chrome versions

• Add conditional GHCR login step when PUSH_IMAGE is true
• Add mirror images task that calls mirror_browser_images_ghcr Makefile target
• Pass GHCR_NAMESPACE and BROWSER_NAME environment variables to mirror task

.github/workflows/release-chrome-versions.yml


5. .github/workflows/release-edge-versions.yml ✨ Enhancement +6/-0

Add GHCR mirroring for Edge versions

• Add conditional GHCR login step when PUSH_IMAGE is true
• Add mirror images task that calls mirror_browser_images_ghcr Makefile target
• Pass GHCR_NAMESPACE and BROWSER_NAME environment variables to mirror task

.github/workflows/release-edge-versions.yml


6. .github/workflows/release-firefox-versions.yml ✨ Enhancement +6/-0

Add GHCR mirroring for Firefox versions

• Add conditional GHCR login step when PUSH_IMAGE is true
• Add mirror images task that calls mirror_browser_images_ghcr Makefile target
• Pass GHCR_NAMESPACE and BROWSER_NAME environment variables to mirror task

.github/workflows/release-firefox-versions.yml


7. Makefile ✨ Enhancement +64/-0

Add Makefile targets for GHCR image mirroring

• Add GHCR_NAMESPACE variable with default value ghcr.io/seleniumhq
• Add tag_and_push_browser_images_ghcr target using docker buildx imagetools create to mirror
 browser images
• Add mirror_browser_images_ghcr target for mirroring individual browser images by name
• Add release_ghcr target to mirror all versioned images (TAG_VERSION, MAJOR, MAJOR.MINOR,
 MAJOR_MINOR_PATCH) and video image
• Add release_ghcr_latest target to mirror all images with latest tag
• Add release_ghcr_nightly target to mirror all images with nightly tag

Makefile


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 24, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (2) 📎 Requirement gaps (1) 📐 Spec deviations (0)

Grey Divider


Action required

1. GHCR_NAMESPACE defaults to seleniumhq 📎 Requirement gap ✓ Correctness
Description
The GHCR namespace is set to ghcr.io/seleniumhq, but the compliance requirement specifies
publishing under ghcr.io/selenium/.... This can break the expected pull path and fails the
required registry naming scheme.
Code

Makefile[40]

+GHCR_NAMESPACE := $(or $(GHCR_NAMESPACE),$(GHCR_NAMESPACE),ghcr.io/seleniumhq)
Evidence
PR Compliance ID 1 requires GHCR images under ghcr.io/selenium/..., but the PR sets the default
GHCR namespace to ghcr.io/seleniumhq, which does not match the required prefix.

Publish Selenium Docker images to both Docker Hub and GitHub Container Registry (GHCR)
Makefile[40-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The default GHCR namespace is `ghcr.io/seleniumhq`, but compliance requires images be published under `ghcr.io/selenium/...`.
## Issue Context
This PR introduces GHCR mirroring; the registry namespace must match the required org/prefix so users can pull images from the documented location.
## Fix Focus Areas
- Makefile[40-40]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. BUILD_DATE unquoted in command 📘 Rule violation ⛯ Reliability
Description
The workflow passes BUILD_DATE=${BUILD_DATE} without quotes, which can cause
word-splitting/globbing if the value ever contains whitespace or special characters. This violates
the requirement for robust shell quoting in scripts/configured commands.
Code

.github/workflows/deploy.yml[166]

+          command: GHCR_NAMESPACE="ghcr.io/${{ github.repository_owner }}" VERSION="${GRID_VERSION}" BUILD_DATE=${BUILD_DATE} make release_ghcr
Evidence
PR Compliance ID 3 requires quoting variable expansions; the added command includes an unquoted
BUILD_DATE assignment.

.github/workflows/deploy.yml[166-166]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A workflow command assigns `BUILD_DATE=${BUILD_DATE}` without quotes, which is not robust against whitespace/globbing.
## Issue Context
This is part of the GHCR mirroring step; failures here can lead to incomplete releases.
## Fix Focus Areas
- .github/workflows/deploy.yml[166-166]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. GHCR login user mismatch🐞 Bug ⛯ Reliability
Description
Workflows log into ghcr.io using github.repository_owner as the username while using
SELENIUM_CI_TOKEN as the password, which can fail authentication if the token belongs to a
different account and block all GHCR mirror steps.
Code

.github/workflows/deploy.yml[R148-150]

+      - name: Login GitHub Container Registry
+        if: github.event.inputs.skip-build-push-image != 'true'
+        run: echo "${{ secrets.SELENIUM_CI_TOKEN }}" | docker login ghcr.io -u "${{ github.repository_owner }}" --password-stdin
Evidence
Multiple workflows perform `docker login ghcr.io -u "${{ github.repository_owner }}"
--password-stdin using secrets.SELENIUM_CI_TOKEN`. The same token is also used for pushing commits
to trunk, indicating it is a high-privilege credential whose owning identity must be consistent
across GitHub and GHCR auth; using the org/repo owner string as the username is not guaranteed to
match the token owner and can cause GHCR auth to fail, preventing release_ghcr* / *_ghcr targets
from pushing manifests.

.github/workflows/deploy.yml[148-166]
.github/workflows/nightly.yml[100-117]
.github/workflows/deploy.yml[221-226]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflows log into GHCR with `-u ${{ github.repository_owner }}` while using `SELENIUM_CI_TOKEN` as the password. If that token belongs to a bot/user (common for a PAT used to push commits), the GHCR username must match the token owner; otherwise `docker login` may fail and all subsequent mirroring steps will fail.
### Issue Context
This affects deploy/nightly and browser-version release workflows that then invoke `make release_ghcr*` and `make *_ghcr` targets.
### Fix Focus Areas
- .github/workflows/deploy.yml[148-166]
- .github/workflows/nightly.yml[100-117]
- .github/workflows/release-chrome-versions.yml[115-117]
- .github/workflows/release-chrome-for-testing-versions.yml[115-117]
- .github/workflows/release-edge-versions.yml[115-117]
- .github/workflows/release-firefox-versions.yml[115-117]
### Suggested change
Introduce a dedicated secret/var for the GHCR username (the account that owns `SELENIUM_CI_TOKEN`) and use it in all `docker login ghcr.io` steps, e.g.:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Mirror can silently no-op 🐞 Bug ✓ Correctness
Description
The new Makefile GHCR mirroring targets can exit successfully without mirroring anything when
docker images returns no tags, because the grep | while pipeline returns success even on empty
input.
Code

Makefile[R472-492]

+tag_and_push_browser_images_ghcr:
+	for image in node-chrome standalone-chrome \
+	    node-chromium standalone-chromium \
+	    node-chrome-for-testing standalone-chrome-for-testing \
+	    node-edge standalone-edge \
+	    node-firefox standalone-firefox; do \
+	  docker images --format "{{.Tag}}" "$(NAME)/$$image" | grep -v "^<none>$$" | while IFS= read -r tag; do \
+	    docker buildx imagetools create \
+	      --tag $(GHCR_NAMESPACE)/$$image:$$tag \
+	      docker.io/$(NAME)/$$image:$$tag ; \
+	  done ; \
+	done
+
+mirror_browser_images_ghcr:
+	for image in node-$(BROWSER_NAME) standalone-$(BROWSER_NAME); do \
+	  docker images --format "{{.Tag}}" "$(NAME)/$$image" | grep -v "^<none>$$" | while IFS= read -r tag; do \
+	    docker buildx imagetools create \
+	      --tag $(GHCR_NAMESPACE)/$$image:$$tag \
+	      docker.io/$(NAME)/$$image:$$tag ; \
+	  done ; \
+	done
Evidence
Both tag_and_push_browser_images_ghcr and mirror_browser_images_ghcr enumerate tags via `docker
images ... | grep ... | while read tag; do ...; done`. If no tags exist locally for a given image,
grep returns non-zero but the pipeline’s exit code is that of the final while (which exits 0 on
empty input), so the recipe succeeds while performing zero imagetools create operations. This can
leave GHCR missing expected tags without failing the workflow.

Makefile[472-492]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`tag_and_push_browser_images_ghcr` / `mirror_browser_images_ghcr` can succeed while mirroring nothing if no local tags are found for the images, due to pipeline exit status behavior.
### Issue Context
Current implementation:
- lists tags with `docker images --format "{{.Tag}}" "$(NAME)/$image"`
- filters with `grep -v "^<none>$"`
- iterates via `while read -r tag; do ...; done`
This pattern returns success even with zero tags.
### Fix Focus Areas
- Makefile[472-492]
### Suggested fix
Capture tags into a variable and explicitly error if empty, e.g.:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Pipelines lack pipefail 📘 Rule violation ⛯ Reliability
Description
The new Makefile mirroring targets use piped grep | while ... loops without enabling
pipefail/fail-fast shell settings, which can hide failures in intermediate commands and lead to
incomplete mirroring. This is fragile command sequencing for a release-critical step.
Code

Makefile[R472-492]

+tag_and_push_browser_images_ghcr:
+	for image in node-chrome standalone-chrome \
+	    node-chromium standalone-chromium \
+	    node-chrome-for-testing standalone-chrome-for-testing \
+	    node-edge standalone-edge \
+	    node-firefox standalone-firefox; do \
+	  docker images --format "{{.Tag}}" "$(NAME)/$$image" | grep -v "^<none>$$" | while IFS= read -r tag; do \
+	    docker buildx imagetools create \
+	      --tag $(GHCR_NAMESPACE)/$$image:$$tag \
+	      docker.io/$(NAME)/$$image:$$tag ; \
+	  done ; \
+	done
+
+mirror_browser_images_ghcr:
+	for image in node-$(BROWSER_NAME) standalone-$(BROWSER_NAME); do \
+	  docker images --format "{{.Tag}}" "$(NAME)/$$image" | grep -v "^<none>$$" | while IFS= read -r tag; do \
+	    docker buildx imagetools create \
+	      --tag $(GHCR_NAMESPACE)/$$image:$$tag \
+	      docker.io/$(NAME)/$$image:$$tag ; \
+	  done ; \
+	done
Evidence
PR Compliance ID 3 calls for robust command chaining; the added recipes rely on pipelines and loops
where failures may not reliably propagate without pipefail/explicit chaining.

Makefile[472-492]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Makefile targets added for GHCR mirroring use pipelines (`grep | while ...`) without `pipefail`/fail-fast settings, which can mask command failures.
## Issue Context
These targets publish/mirror release images; silent failures can leave GHCR incomplete while the workflow appears successful.
## Fix Focus Areas
- Makefile[472-492]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. No retry on mirroring 🐞 Bug ⛯ Reliability
Description
The browser-version release workflows mirror to GHCR without the retry wrapper used elsewhere, so
transient registry/network failures can fail the job after Docker Hub images were already pushed.
Code

.github/workflows/release-chrome-versions.yml[R154-156]

+      - name: Mirror images to GHCR
+        if: env.PUSH_IMAGE == 'true'
+        run: GHCR_NAMESPACE="ghcr.io/${{ github.repository_owner }}" BROWSER_NAME="${BROWSER_NAME}" make mirror_browser_images_ghcr
Evidence
deploy.yml/nightly.yml wrap GHCR mirroring in nick-invision/retry@master with multiple
attempts, but the release-*-versions workflows call make mirror_browser_images_ghcr directly
without retries. Since docker buildx imagetools create performs remote registry operations, this
increases flake risk and can leave Docker Hub and GHCR out of sync on intermittent failures.

.github/workflows/release-chrome-versions.yml[150-156]
.github/workflows/deploy.yml[159-166]
.github/workflows/nightly.yml[111-117]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Browser-version release workflows mirror to GHCR with a single `run:` command and no retry logic.
### Issue Context
Deploy/nightly already use `nick-invision/retry@master` around mirroring steps; release-*-versions do not.
### Fix Focus Areas
- .github/workflows/release-chrome-versions.yml[154-156]
- .github/workflows/release-chrome-for-testing-versions.yml[154-156]
- .github/workflows/release-edge-versions.yml[154-156]
- .github/workflows/release-firefox-versions.yml[154-156]
### Suggested fix
Change the mirror step to use `nick-invision/retry@master` similarly to deploy/nightly, e.g.:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Signed-off-by: Viet Nguyen Duc <nguyenducviet4496@gmail.com>
KEDA_BASED_TAG := $(or $(KEDA_BASED_TAG),$(KEDA_BASED_TAG),2.19.0)
TEST_PATCHED_KEDA := $(or $(TEST_PATCHED_KEDA),$(TEST_PATCHED_KEDA),false)
TRACING_EXPORTER_ENDPOINT := $(or $(TRACING_EXPORTER_ENDPOINT),$(TRACING_EXPORTER_ENDPOINT),http://\$$KUBERNETES_NODE_HOST_IP:4317)
GHCR_NAMESPACE := $(or $(GHCR_NAMESPACE),$(GHCR_NAMESPACE),ghcr.io/seleniumhq)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. ghcr_namespace defaults to seleniumhq 📎 Requirement gap ✓ Correctness

The GHCR namespace is set to ghcr.io/seleniumhq, but the compliance requirement specifies
publishing under ghcr.io/selenium/.... This can break the expected pull path and fails the
required registry naming scheme.
Agent Prompt
## Issue description
The default GHCR namespace is `ghcr.io/seleniumhq`, but compliance requires images be published under `ghcr.io/selenium/...`.

## Issue Context
This PR introduces GHCR mirroring; the registry namespace must match the required org/prefix so users can pull images from the documented location.

## Fix Focus Areas
- Makefile[40-40]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

timeout_minutes: 30
max_attempts: 5
retry_wait_seconds: 300
command: GHCR_NAMESPACE="ghcr.io/${{ github.repository_owner }}" VERSION="${GRID_VERSION}" BUILD_DATE=${BUILD_DATE} make release_ghcr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. build_date unquoted in command 📘 Rule violation ⛯ Reliability

The workflow passes BUILD_DATE=${BUILD_DATE} without quotes, which can cause
word-splitting/globbing if the value ever contains whitespace or special characters. This violates
the requirement for robust shell quoting in scripts/configured commands.
Agent Prompt
## Issue description
A workflow command assigns `BUILD_DATE=${BUILD_DATE}` without quotes, which is not robust against whitespace/globbing.

## Issue Context
This is part of the GHCR mirroring step; failures here can lead to incomplete releases.

## Fix Focus Areas
- .github/workflows/deploy.yml[166-166]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +472 to +492
tag_and_push_browser_images_ghcr:
for image in node-chrome standalone-chrome \
node-chromium standalone-chromium \
node-chrome-for-testing standalone-chrome-for-testing \
node-edge standalone-edge \
node-firefox standalone-firefox; do \
docker images --format "{{.Tag}}" "$(NAME)/$$image" | grep -v "^<none>$$" | while IFS= read -r tag; do \
docker buildx imagetools create \
--tag $(GHCR_NAMESPACE)/$$image:$$tag \
docker.io/$(NAME)/$$image:$$tag ; \
done ; \
done

mirror_browser_images_ghcr:
for image in node-$(BROWSER_NAME) standalone-$(BROWSER_NAME); do \
docker images --format "{{.Tag}}" "$(NAME)/$$image" | grep -v "^<none>$$" | while IFS= read -r tag; do \
docker buildx imagetools create \
--tag $(GHCR_NAMESPACE)/$$image:$$tag \
docker.io/$(NAME)/$$image:$$tag ; \
done ; \
done
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Mirror can silently no-op 🐞 Bug ✓ Correctness

The new Makefile GHCR mirroring targets can exit successfully without mirroring anything when
docker images returns no tags, because the grep | while pipeline returns success even on empty
input.
Agent Prompt
### Issue description
`tag_and_push_browser_images_ghcr` / `mirror_browser_images_ghcr` can succeed while mirroring nothing if no local tags are found for the images, due to pipeline exit status behavior.

### Issue Context
Current implementation:
- lists tags with `docker images --format "{{.Tag}}" "$(NAME)/$image"`
- filters with `grep -v "^<none>$"`
- iterates via `while read -r tag; do ...; done`
This pattern returns success even with zero tags.

### Fix Focus Areas
- Makefile[472-492]

### Suggested fix
Capture tags into a variable and explicitly error if empty, e.g.:

```make
mirror_browser_images_ghcr:
	for image in node-$(BROWSER_NAME) standalone-$(BROWSER_NAME); do \
	  tags="$$(docker images --format '{{.Tag}}' '$(NAME)/'$$image | grep -v '^<none>$$' || true)"; \
	  if [ -z "$$tags" ]; then \
	    echo "No local tags found for $(NAME)/$$image; cannot mirror" >&2; \
	    exit 1; \
	  fi; \
	  for tag in $$tags; do \
	    docker buildx imagetools create --tag $(GHCR_NAMESPACE)/$$image:$$tag docker.io/$(NAME)/$$image:$$tag; \
	  done; \
	done
```

Optionally add `set -e`/`pipefail` within the recipe to ensure failures propagate.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If space is a concern on ghcr, perhaps we should skip uploading nightly builds to ghcri.

Signed-off-by: Viet Nguyen Duc <nguyenducviet4496@gmail.com>
Signed-off-by: Viet Nguyen Duc <nguyenducviet4496@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🚀 Feature]: Push containers to both Docker Hub and GHCR

2 participants