Skip to content

ci: publish Docker images to GHCR alongside Docker Hub#7569

Merged
JohnMcLear merged 2 commits intodevelopfrom
add-ghcr-publish
Apr 20, 2026
Merged

ci: publish Docker images to GHCR alongside Docker Hub#7569
JohnMcLear merged 2 commits intodevelopfrom
add-ghcr-publish

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

Summary

Adds ghcr.io/ether/etherpad as a second publish target for release tags, reusing the existing docker/metadata-action step so identical SemVer tags (2.6.1, 2.6, 2, latest) are pushed to both registries.

Why

Downstream consumers — notably Helm chart repositories — hit Docker Hub's anonymous pull rate limits in Kubernetes clusters. GHCR has no equivalent limits, and Docker Hub is still under flux re: future pricing changes. Publishing to both gives downstreams a choice and costs us nothing; GITHUB_TOKEN already has enough scope to push to the org's own GHCR namespace, so no new secrets.

Concrete trigger: trueforge-org/truecharts#47234 — TrueCharts maintainers pushed back on a chart update that pointed at Docker Hub, citing rate-limiting pain. Publishing to GHCR solves this for them and for anyone else in the same position.

Changes

.github/workflows/docker.yml:

  • Added packages: write to permissions: (required for GHCR push via GITHUB_TOKEN).
  • Added ghcr.io/ether/etherpad to the images: list in the docker/metadata-action step.
  • Added a Log in to GHCR step using the built-in GITHUB_TOKEN — runs in parallel with the existing Docker Hub login, same if: github.event_name == 'push' gate.

Docker Hub remains the primary/canonical source. GHCR is a mirror.

Testing

  • Workflow syntax review only — the build-push-action step already supports multiple tags across registries, so no behavioural change for Docker Hub.
  • Will be validated by the first push to a release tag after merge.

Follow-up

This only publishes future release tags. If downstream needs 2.6.1 on GHCR before the next release, it must be mirrored separately (e.g. skopeo copy --all docker://docker.io/etherpad/etherpad:2.6.1 docker://ghcr.io/ether/etherpad:2.6.1). Happy to do that one-shot after merge.

Adds ghcr.io/ether/etherpad as a second publish target on release tags,
reusing the existing docker/metadata-action step so the same SemVer tags
(e.g. 2.6.1, 2.6, 2, latest) are pushed to both registries.

Motivation: downstream consumers (Helm charts in particular) hit Docker
Hub anonymous pull rate limits. GHCR has no such limits and the
workflow already runs with GITHUB_TOKEN, so this is additive with no
new secrets required.

Docker Hub remains the primary/canonical source; GHCR is a mirror.

Note: this only affects future release tags. The 2.6.1 tag already on
Docker Hub will need to be mirrored separately (e.g. via skopeo) if
downstream needs it on GHCR before the next release.
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Publish Docker images to GHCR alongside Docker Hub

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds GHCR (ghcr.io/ether/etherpad) as secondary Docker image registry
• Reuses existing metadata action for consistent SemVer tagging across registries
• Adds packages: write permission for GITHUB_TOKEN GHCR push access
• Implements GHCR login step using built-in GitHub credentials
Diagram
flowchart LR
  A["Docker metadata-action"] -->|generates tags| B["Docker Hub<br/>etherpad/etherpad"]
  A -->|generates tags| C["GHCR<br/>ghcr.io/ether/etherpad"]
  D["GITHUB_TOKEN"] -->|authenticates| C
  E["DOCKERHUB_TOKEN"] -->|authenticates| B
Loading

Grey Divider

File Changes

1. .github/workflows/docker.yml ✨ Enhancement +12/-1

Add GHCR as secondary Docker image registry

• Added packages: write permission to enable GHCR push via GITHUB_TOKEN
• Extended docker/metadata-action images list to include ghcr.io/ether/etherpad
• Added new login step for GHCR using docker/login-action with GITHUB_TOKEN
• Both Docker Hub and GHCR now receive identical SemVer tags on release pushes

.github/workflows/docker.yml


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects bot commented Apr 20, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Overprivileged PR token🐞 Bug ⛨ Security
Description
The workflow runs on pull_request but now grants GITHUB_TOKEN packages: write, so untrusted
PR-controlled code executed in the Test step can publish/overwrite GHCR packages despite the
explicit GHCR login/push steps being gated to push. This increases CI blast radius and enables
supply-chain compromise via CI token abuse.
Code

.github/workflows/docker.yml[17]

+  packages: write
Evidence
The workflow is triggered by pull_request, but permissions are set at the workflow level and apply
to all jobs/steps for that run. The Test step executes repository code and scripts (PR-controlled
on pull_request runs), meaning a malicious change can use the elevated token to authenticate and
push packages/images even though the provided docker/login-action step is `if: github.event_name
== 'push'`.

.github/workflows/docker.yml[2-5]
.github/workflows/docker.yml[15-17]
.github/workflows/docker.yml[64-81]
.github/workflows/docker.yml[103-115]

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 workflow grants `packages: write` at the workflow level while still running on `pull_request`, which exposes package publish capability to PR-executed code.
### Issue Context
Top-level `permissions:` apply to the entire workflow run (including `pull_request` runs), even when individual publish steps are gated by `if: github.event_name == 'push'`.
### Fix Focus Areas
- .github/workflows/docker.yml[2-17]
- .github/workflows/docker.yml[64-122]
### Proposed fix
1. Keep the PR test job with minimal permissions (for example only `contents: read`).
2. Create a separate `publish` job that runs only on `push` (use `if: github.event_name == 'push'`) and set `permissions: packages: write` at the job level for that job only.
3. Optionally make `publish` depend on the test job (`needs:`) so pushes only publish after tests pass.

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



Advisory comments

2. Docs miss GHCR option🐞 Bug ⚙ Maintainability
Description
After adding ghcr.io/ether/etherpad as a publish target, Docker documentation still states the
official image is only on Docker Hub and provides only Docker Hub pull commands. This makes the new
GHCR mirror effectively undiscoverable for users relying on project docs.
Code

.github/workflows/docker.yml[R88-90]

+          images: |
+            etherpad/etherpad
+            ghcr.io/ether/etherpad
Evidence
The workflow now publishes tags for ghcr.io/ether/etherpad, but the existing Docker documentation
continues to present Docker Hub as the sole official image location and only shows `docker pull
etherpad/etherpad` examples.

.github/workflows/docker.yml[83-96]
doc/docker.md[1-13]

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

## Issue description
Documentation only references Docker Hub, but the workflow now publishes the same tags to GHCR.
### Issue Context
Users following docs will not discover the GHCR mirror and may continue to hit Docker Hub rate limits.
### Fix Focus Areas
- doc/docker.md[1-13]
- README.md[104-113]
### Proposed fix
Update Docker docs (and optionally README) to mention GHCR as an alternative mirror and add example pull commands such as:
- `docker pull ghcr.io/ether/etherpad:latest`
- `docker pull ghcr.io/ether/etherpad:<version>`
while keeping Docker Hub as canonical if desired.

ⓘ 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

Comment thread .github/workflows/docker.yml Outdated
Two fixes from the qodo code review on #7569:

1. Overprivileged PR token (security). The original change set
   'packages: write' at workflow level, which meant pull_request runs
   (whose Test step executes PR-controlled code) also inherited push
   access to GHCR. Splits the workflow into two jobs:
     - build-test: runs on pull_request and push with contents:read
       only. Does the single-arch load+test as before.
     - publish: needs build-test, runs only on push with
       packages:write. Does the multi-arch build-and-push, Docker Hub
       description update, and ether-charts bump.
   Docker Hub login is also now gated by job-level 'if' (same effect
   as the previous step-level 'if').

2. Docs miss GHCR option. Updates doc/docker.md and README.md to
   document the GHCR mirror alongside Docker Hub with equivalent pull
   examples, so downstream users discovering via docs can choose the
   mirror to avoid Docker Hub rate limits.
@JohnMcLear JohnMcLear merged commit c2e6938 into develop Apr 20, 2026
37 checks passed
@JohnMcLear JohnMcLear deleted the add-ghcr-publish branch April 20, 2026 09:19
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.

1 participant