11name : Docker build and push
22
3- # limit concurrency
3+ # limit concurrency per ref; cancel in-progress runs for PRs
44# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#examples-using-concurrency-and-the-default-behavior
5- concurrency : docker_mmgis_main
5+ concurrency :
6+ group : docker-${{ github.ref }}
7+ cancel-in-progress : ${{ github.event_name == 'pull_request' }}
68
79on :
810 push :
3032 IMAGE_SLUG : ${{ github.repository }}
3133
3234jobs :
33- # Generate shared tags for both architectures
35+ # Generate shared tags for both architectures as a JSON array.
3436 # The image tag pattern is:
35- # for pull-requests: <PATCH_VERSION>-<DATE>-<PR_NUMBER>, eg: 1.35.2 -20210125-25
37+ # for pull-requests: <PATCH_VERSION>-<DATE>-<PR_NUMBER>, eg: 4.2.33 -20210125-25
3638 # for tags: <TAG>
3739 # for `master` branch: latest,<PATCH_VERSION>-latest,<MINOR_VERSION>-latest,<MAJOR_VERSION>-latest,<PATCH_VERSION>-<DATE>-<SHA>
3840 # for `development` branch: development,<MAJOR_VERSION>-development,<PATCH_VERSION>-<DATE>-<SHA>
3941 # for releases: release,<PATCH_VERSION>-release,<MINOR_VERSION>-release,<MAJOR_VERSION>-release,<PATCH_VERSION>-<DATE>-<SHA>
40- # Version is parsed from package.json
42+ # Version is parsed from package.json (date suffix stripped before use in tags)
4143 generate-tags :
4244 runs-on : ubuntu-latest
43- if : github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'release'
4445 outputs :
45- registry- tags : ${{ steps.generate.outputs.REGISTRY_TAGS }}
46+ tags : ${{ steps.generate.outputs.TAGS }}
4647 image-id : ${{ steps.generate.outputs.IMAGE_ID }}
4748 steps :
4849 - name : Checkout
49- uses : actions/checkout@v3
50+ uses : actions/checkout@v4
5051
5152 - name : Generate tags
5253 id : generate
5859 # Date
5960 BDATE=$(date +%Y%m%d)
6061
61- # PATCH_VERSION from package.json is like "1.2.3"
62- # MINOR_VERSION like "1.2", MAJOR_VERSION like "1"
63- PATCH_VERSION=$(jq .version -r ./package.json)
62+ # RAW_VERSION from package.json may be "4.2.33-20260327" (date-suffixed by bump-version workflow).
63+ # Strip the date suffix before parsing semver components so it doesn't appear twice in image tags.
64+ RAW_VERSION=$(jq .version -r ./package.json)
65+ BASE_VERSION=$(echo $RAW_VERSION | sed 's/-[0-9]\{8\}$//')
66+ PATCH_VERSION=$BASE_VERSION
6467 MINOR_VERSION=${PATCH_VERSION%.*}
6568 MAJOR_VERSION=${MINOR_VERSION%.*}
6669
@@ -86,47 +89,57 @@ jobs:
8689 [ "$VERSION" == "development" ] && VERSION=development
8790 [ "${{ github.event_name }}" == "release" ] && VERSION=release
8891
89- # Compose REGISTRY_TAGS variable for buildx (space-separated with --tag flags )
90- REGISTRY_TAGS="--tag $IMAGE_ID:$VERSION"
92+ SHA_SHORT=$(git rev-parse --short HEAD )
93+ [ "${{ github.event_name }}" == "pull_request" ] && SHA_SHORT=$(echo ${{ github.event.pull_request.head.sha }} | cut -c1-8)
9194
92- # For master branch also supply an extra tag: <PATCH_VERSION>-latest,<MINOR_VERSION>-latest,<MAJOR_VERSION>-latest,<PATCH_VERSION>-<DATE>-<SHA>
93- [ "$VERSION" == "latest" ] && REGISTRY_TAGS="$REGISTRY_TAGS --tag $IMAGE_ID:$PATCH_VERSION-latest --tag $IMAGE_ID:$MINOR_VERSION-latest --tag $IMAGE_ID:$MAJOR_VERSION-latest --tag $IMAGE_ID:$PATCH_VERSION-$BDATE-$(git rev-parse --short HEAD)"
94- [ "$VERSION" == "development" ] && REGISTRY_TAGS="$REGISTRY_TAGS --tag $IMAGE_ID:$MAJOR_VERSION-development --tag $IMAGE_ID:$PATCH_VERSION-$BDATE-$(git rev-parse --short HEAD)"
95- [ "$VERSION" == "release" ] && REGISTRY_TAGS="$REGISTRY_TAGS --tag $IMAGE_ID:$PATCH_VERSION-release --tag $IMAGE_ID:$MINOR_VERSION-release --tag $IMAGE_ID:$MAJOR_VERSION-release --tag $IMAGE_ID:$PATCH_VERSION-$BDATE-$(git rev-parse --short HEAD)"
95+ # Build TAGS as a JSON array of tag names (no --tag flags, no image ID prefix).
96+ # Arch-specific suffixes (-amd64, -arm64) and the combined manifest are applied by later jobs.
97+ # Specific/versioned tags are listed first; floating tags (development, latest, release) are last
98+ # so that GHCR features the floating tag as the most recently published version.
99+ TAGS=$(jq -cn --arg v "$VERSION" '[$v]')
100+ [ "$VERSION" == "latest" ] && TAGS=$(jq -cn \
101+ --arg a "$PATCH_VERSION-$BDATE-$SHA_SHORT" \
102+ --arg b "$MAJOR_VERSION-latest" \
103+ --arg c "$MINOR_VERSION-latest" \
104+ --arg d "$PATCH_VERSION-latest" \
105+ --arg e "latest" \
106+ '[$a,$b,$c,$d,$e]')
107+ [ "$VERSION" == "development" ] && TAGS=$(jq -cn \
108+ --arg a "$PATCH_VERSION-$BDATE-$SHA_SHORT" \
109+ --arg b "$MAJOR_VERSION-development" \
110+ --arg c "development" \
111+ '[$a,$b,$c]')
112+ [ "$VERSION" == "release" ] && TAGS=$(jq -cn \
113+ --arg a "$PATCH_VERSION-$BDATE-$SHA_SHORT" \
114+ --arg b "$MAJOR_VERSION-release" \
115+ --arg c "$MINOR_VERSION-release" \
116+ --arg d "$PATCH_VERSION-release" \
117+ --arg e "release" \
118+ '[$a,$b,$c,$d,$e]')
96119
97120 echo IMAGE_ID=$IMAGE_ID
98- echo VERSION=$VERSION
99- echo REGISTRY_TAGS=$REGISTRY_TAGS
100- echo headref=${{ github.head_ref }}
101-
102- SHA_SHORT=${{ github.sha }}
103- [ "${{ github.event_name }}" == "pull_request" ] && SHA_SHORT=$(echo ${{ github.event.pull_request.head.sha }} | cut -c1-8)
121+ echo TAGS=$TAGS
104122
105- echo "Final image tags to be pushed:"
106- echo $REGISTRY_TAGS
107- echo "REGISTRY_TAGS=$REGISTRY_TAGS" >> $GITHUB_OUTPUT
123+ echo "TAGS=$TAGS" >> $GITHUB_OUTPUT
108124 echo "IMAGE_ID=$IMAGE_ID" >> $GITHUB_OUTPUT
109- echo "REGISTRY_TAGS_VERSION=$VERSION" >> $GITHUB_OUTPUT
110- echo "REGISTRY_TAGS_PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT
111- echo "SHA_SHORT=$SHA_SHORT" >> $GITHUB_OUTPUT
112125
113126 # Build and push ARM64 image on ARM64 runner
114127 build-arm64 :
115128 needs : generate-tags
116129 runs-on : ubuntu-24.04-arm # ARM64 runner for native ARM64 builds
117130 steps :
118131 - name : Checkout
119- uses : actions/checkout@v3
132+ uses : actions/checkout@v4
120133
121134 - name : Login to GHCR
122- uses : docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
135+ uses : docker/login-action@v3
123136 with :
124137 registry : ghcr.io
125138 username : ${{ github.actor }}
126139 password : ${{ secrets.GITHUB_TOKEN }}
127140
128141 - name : Set up Docker Buildx
129- uses : docker/setup-buildx-action@v2
142+ uses : docker/setup-buildx-action@v3
130143 with :
131144 driver-opts : |
132145 image=moby/buildkit:latest
@@ -135,42 +148,43 @@ jobs:
135148 env :
136149 DOCKER_BUILDKIT : 1
137150 run : |
138- # Generate ARM64-specific tags by adding -arm64 suffix
139- ARM64_TAGS="${{ needs.generate-tags.outputs.registry-tags }}-arm64"
151+ IMAGE_ID="${{ needs.generate-tags.outputs.image-id }}"
152+
153+ # Build --tag flags: add -arm64 suffix to every tag
154+ TAG_FLAGS=$(echo '${{ needs.generate-tags.outputs.tags }}' | jq -r '.[]' | \
155+ while read tag; do echo "--tag $IMAGE_ID:$tag-arm64"; done | tr '\n' ' ')
140156
141157 # Only disable cache for releases
142158 CACHE_FLAG=""
143- if [ "${{ github.event_name }}" = "release" ]; then
144- CACHE_FLAG="--no-cache"
145- fi
159+ [ "${{ github.event_name }}" = "release" ] && CACHE_FLAG="--no-cache"
146160
147161 docker buildx build \
148- ${ARM64_TAGS} \
162+ $TAG_FLAGS \
149163 --push \
150164 --platform linux/arm64 \
151- --cache-from type=registry,ref=${{ needs.generate-tags.outputs.image-id }} :buildcache-arm64 \
152- --cache-to type=registry,ref=${{ needs.generate-tags.outputs.image-id }} :buildcache-arm64,mode=max \
165+ --cache-from type=registry,ref=$IMAGE_ID :buildcache-arm64 \
166+ --cache-to type=registry,ref=$IMAGE_ID :buildcache-arm64,mode=max \
153167 --build-arg PUBLIC_URL_ARG=${{ secrets.PUBLIC_URL }} \
154- ${ CACHE_FLAG} \
168+ $CACHE_FLAG \
155169 .
156170
157171 # Build and push AMD64 image on x64 runner
158172 build-amd64 :
159- needs : [ generate-tags, build-arm64] # Build amd64 last so that it shows as the latest
173+ needs : generate-tags
160174 runs-on : ubuntu-latest # x64 runner for native AMD64 builds
161175 steps :
162176 - name : Checkout
163- uses : actions/checkout@v3
177+ uses : actions/checkout@v4
164178
165179 - name : Login to GHCR
166- uses : docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
180+ uses : docker/login-action@v3
167181 with :
168182 registry : ghcr.io
169183 username : ${{ github.actor }}
170184 password : ${{ secrets.GITHUB_TOKEN }}
171185
172186 - name : Set up Docker Buildx
173- uses : docker/setup-buildx-action@v2
187+ uses : docker/setup-buildx-action@v3
174188 with :
175189 driver-opts : |
176190 image=moby/buildkit:latest
@@ -179,21 +193,49 @@ jobs:
179193 env :
180194 DOCKER_BUILDKIT : 1
181195 run : |
182- # Generate AMD64-specific tags by adding -amd64 suffix
183- AMD64_TAGS="${{ needs.generate-tags.outputs.registry-tags }}-amd64"
196+ IMAGE_ID="${{ needs.generate-tags.outputs.image-id }}"
197+
198+ # Build --tag flags: add -amd64 suffix to every tag
199+ TAG_FLAGS=$(echo '${{ needs.generate-tags.outputs.tags }}' | jq -r '.[]' | \
200+ while read tag; do echo "--tag $IMAGE_ID:$tag-amd64"; done | tr '\n' ' ')
184201
185202 # Only disable cache for releases
186203 CACHE_FLAG=""
187- if [ "${{ github.event_name }}" = "release" ]; then
188- CACHE_FLAG="--no-cache"
189- fi
204+ [ "${{ github.event_name }}" = "release" ] && CACHE_FLAG="--no-cache"
190205
191206 docker buildx build \
192- ${AMD64_TAGS} \
207+ $TAG_FLAGS \
193208 --push \
194209 --platform linux/amd64 \
195- --cache-from type=registry,ref=${{ needs.generate-tags.outputs.image-id }} :buildcache-amd64 \
196- --cache-to type=registry,ref=${{ needs.generate-tags.outputs.image-id }} :buildcache-amd64,mode=max \
210+ --cache-from type=registry,ref=$IMAGE_ID :buildcache-amd64 \
211+ --cache-to type=registry,ref=$IMAGE_ID :buildcache-amd64,mode=max \
197212 --build-arg PUBLIC_URL_ARG=${{ secrets.PUBLIC_URL }} \
198- ${ CACHE_FLAG} \
213+ $CACHE_FLAG \
199214 .
215+
216+ # Create multi-arch manifests combining the AMD64 and ARM64 images
217+ create-manifests :
218+ needs : [generate-tags, build-amd64, build-arm64]
219+ runs-on : ubuntu-latest
220+ steps :
221+ - name : Login to GHCR
222+ uses : docker/login-action@v3
223+ with :
224+ registry : ghcr.io
225+ username : ${{ github.actor }}
226+ password : ${{ secrets.GITHUB_TOKEN }}
227+
228+ - name : Set up Docker Buildx
229+ uses : docker/setup-buildx-action@v3
230+
231+ - name : Create multi-arch manifests
232+ run : |
233+ IMAGE_ID="${{ needs.generate-tags.outputs.image-id }}"
234+
235+ echo '${{ needs.generate-tags.outputs.tags }}' | jq -r '.[]' | while read tag; do
236+ echo "Creating manifest for $IMAGE_ID:$tag"
237+ docker buildx imagetools create \
238+ -t $IMAGE_ID:$tag \
239+ $IMAGE_ID:$tag-amd64 \
240+ $IMAGE_ID:$tag-arm64
241+ done
0 commit comments