ci: publish the image to the forge's own registry too

fd1515b · Gabriel Morell · 2026-09-16 03:03

4 files +65 -4
Message
{commit_body(@commit)}

Files changed

modified .github/workflows/ci.yml
+56 −4
@@ -77,6 +77,54 @@ jobs:
77 77 - uses: actions/checkout@v4
78 78 - uses: docker/setup-buildx-action@v3
79 79
80 + # The forge's own registry, when this is running on one. Unlike the
81 + # three public registries below it needs no stored credentials: the
82 + # per-job token is already authorized to push to this repository's
83 + # own image path and nowhere else.
84 + #
85 + # `vars.CI_REGISTRY` is what says whether there is one -- on a forge
86 + # that doesn't set it, or on GitHub, every forge step below drops
87 + # out and the public registries are unaffected.
88 + - name: Resolve the forge registry
89 + id: forge
90 + env:
91 + CI_REGISTRY: ${{ vars.CI_REGISTRY }}
92 + run: |
93 + set -euo pipefail
94 +
95 + images=(
96 + "ghcr.io/$GITHUB_REPOSITORY"
97 + "docker.io/neiam/diogramos"
98 + "quay.io/neiam/diogramos"
99 + )
100 +
101 + if [ -n "${CI_REGISTRY:-}" ]; then
102 + forge_image="$CI_REGISTRY/$GITHUB_REPOSITORY"
103 + echo "publishing to the forge registry at $forge_image"
104 +
105 + images+=("$forge_image")
106 + echo "registry=$CI_REGISTRY" >> "$GITHUB_OUTPUT"
107 + echo "image=$forge_image" >> "$GITHUB_OUTPUT"
108 + else
109 + echo "No CI_REGISTRY variable set -- skipping the forge registry."
110 + fi
111 +
112 + {
113 + echo "images<<IMAGES_EOF"
114 + printf '%s\n' "${images[@]}"
115 + echo "IMAGES_EOF"
116 + } >> "$GITHUB_OUTPUT"
117 +
118 + # The token endpoint ignores the username but login-action requires
119 + # it to be non-empty.
120 + - name: Log in to the forge registry
121 + if: steps.forge.outputs.registry != '' && github.event_name != 'pull_request'
122 + uses: docker/login-action@v3
123 + with:
124 + registry: ${{ steps.forge.outputs.registry }}
125 + username: gitgud-actions
126 + password: ${{ github.token }}
127 +
80 128 # GHCR uses the built-in token. Docker Hub and Quay creds come from
81 129 # org-wide secrets (DOCKERHUB_USER/DOCKERHUB_TOKEN, QUAY_USERNAME/QUAY_TOKEN).
82 130 - name: Log in to GHCR
@@ -106,10 +154,7 @@ jobs:
106 154 - id: meta
107 155 uses: docker/metadata-action@v5
108 156 with:
109 − images: |
110 − ghcr.io/${{ github.repository }}
111 − docker.io/neiam/diogramos
112 − quay.io/neiam/diogramos
157 + images: ${{ steps.forge.outputs.images }}
113 158 tags: |
114 159 type=ref,event=branch
115 160 type=ref,event=pr
@@ -118,6 +163,11 @@ jobs:
118 163 type=sha
119 164 type=raw,value=latest,enable={{is_default_branch}}
120 165
166 + # The layer cache lives in the forge registry alongside the image.
167 + # `type=gha` is not an option here: the forge's RunnerJwt carries no
168 + # `ac` claim, so the Actions cache service rejects it. Writing the
169 + # cache is gated on a real push -- a pull request's token can read
170 + # that path but not write it.
121 171 - uses: docker/build-push-action@v6
122 172 with:
123 173 context: .
@@ -125,3 +175,5 @@ jobs:
125 175 push: ${{ github.event_name != 'pull_request' }}
126 176 tags: ${{ steps.meta.outputs.tags }}
127 177 labels: ${{ steps.meta.outputs.labels }}
178 + cache-from: ${{ steps.forge.outputs.image != '' && format('type=registry,ref={0}:buildcache', steps.forge.outputs.image) || '' }}
179 + cache-to: ${{ steps.forge.outputs.image != '' && github.event_name != 'pull_request' && format('type=registry,ref={0}:buildcache,mode=max', steps.forge.outputs.image) || '' }}
modified README.md
+9 −0
@@ -45,6 +45,8 @@ mix run -e 'Diogramos.Release.init("you@example.com", "a strong password")'
45 45
46 46 CI publishes a multi-registry image on every push to `master`:
47 47
48 +- the forge's own registry, `$CI_REGISTRY/neiam/diogramos`, when the repository
49 + is hosted on one that sets `CI_REGISTRY`
48 50 - `ghcr.io/neiam/diogramos`
49 51 - `docker.io/neiam/diogramos`
50 52 - `quay.io/neiam/diogramos`
@@ -52,6 +54,13 @@ CI publishes a multi-registry image on every push to `master`:
52 54 Tags: `latest` (default branch), `vX.Y.Z` + `X.Y` (git tags), the branch name,
53 55 and the commit SHA.
54 56
57 +The forge registry is the one that needs no setup: the per-job `github.token`
58 +is already authorized to push to that repository's own image path and nowhere
59 +else, so there is no secret to create. The three public registries need
60 +`GHCR_TOKEN`, `DOCKERHUB_USER`/`DOCKERHUB_TOKEN` and
61 +`QUAY_USERNAME`/`QUAY_TOKEN`; each login is skipped on pull requests, and a
62 +missing `CI_REGISTRY` simply drops the forge registry from the list.
63 +
55 64 ```sh
56 65 docker pull ghcr.io/neiam/diogramos:latest
57 66

Parents: e7d9f29