99 pull_request :
1010 branches :
1111 - " master"
12+ workflow_dispatch :
13+ inputs :
14+ ref :
15+ description : " Tag to build (e.g. v0.1.0)"
16+ required : true
1217
1318env :
1419 IMAGE_NAME : ${{ github.repository }}
1520 REGISTRY : ghcr.io
1621
1722jobs :
18- build :
19- name : Build Docker image
20- runs-on : ubuntu-latest
23+ build-riverui :
24+ name : " Build Docker image: riverui "
25+ runs-on : ${{ matrix.runner }}
2126 strategy :
2227 matrix :
23- docker_platform :
24- - linux/amd64
25- - linux/arm64
26- - linux/arm64/v8
28+ include :
29+ - docker_platform : linux/amd64
30+ runner : ubuntu-latest
31+ - docker_platform : linux/arm64
32+ runner : ubuntu-24.04-arm64
2733 outputs :
2834 tags : ${{ steps.meta.outputs.tags }}
2935 labels : ${{ steps.meta.outputs.labels }}
@@ -36,15 +42,14 @@ jobs:
3642 steps :
3743 - name : Checkout
3844 uses : actions/checkout@v4
45+ with :
46+ ref : ${{ inputs.ref || github.ref }}
3947
4048 - name : Prepare
4149 run : |
4250 platform=${{ matrix.docker_platform }}
4351 echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
4452
45- - name : Set up QEMU
46- uses : docker/setup-qemu-action@v3
47-
4853 - name : Set up Docker Buildx
4954 uses : docker/setup-buildx-action@v3
5055
5964 id : meta
6065 uses : docker/metadata-action@v5
6166 with :
62- images : ghcr.io/${{ github.repository }}
67+ images : ghcr.io/${{ env.IMAGE_NAME }}
6368 labels : |
6469 org.opencontainers.image.source=https://github.com/riverqueue/riverui
6570 org.opencontainers.image.description="River UI is a web-based user interface for River, a fast and reliable background job system."
6974 type=ref,event=pr
7075 type=semver,pattern={{version}}
7176 type=semver,pattern={{major}}.{{minor}}
77+ type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
7278
7379 - name : Build and push to GitHub Container Registry
7480 id : build
@@ -98,30 +104,108 @@ jobs:
98104 - name : Upload digest
99105 uses : actions/upload-artifact@v4
100106 with :
101- name : digests-${{ env.PLATFORM_PAIR }}
107+ name : digests-oss- ${{ env.PLATFORM_PAIR }}
102108 path : /tmp/digests/*
103109 if-no-files-found : error
104110 retention-days : 1
105111
106- merge :
107- name : Merge and publish image manifests
108- runs-on : ubuntu-latest
109- needs :
110- - build
112+ build-riverproui :
113+ name : " Build Docker image: riverproui"
114+ runs-on : ${{ matrix.runner }}
115+ strategy :
116+ matrix :
117+ include :
118+ - docker_platform : linux/amd64
119+ runner : ubuntu-latest
120+ - docker_platform : linux/arm64
121+ runner : ubuntu-24.04-arm64
122+
111123 permissions :
124+ attestations : write
112125 contents : read
113126 id-token : write
114127 packages : write
115128
116129 steps :
117130 - name : Checkout
118131 uses : actions/checkout@v4
132+ with :
133+ ref : ${{ inputs.ref || github.ref }}
134+
135+ - name : Prepare
136+ run : |
137+ platform=${{ matrix.docker_platform }}
138+ echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
139+
140+ - name : Set TAG
141+ run : echo "TAG=${{ inputs.ref || github.ref_name }}" >> $GITHUB_ENV
142+
143+ - name : Validate TAG
144+ if : startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
145+ run : |
146+ if [[ ! "$TAG" == v* ]]; then
147+ echo "Tag must start with 'v'"
148+ exit 1
149+ fi
150+
151+ - name : Set up QEMU
152+ uses : docker/setup-qemu-action@v3
153+
154+ - name : Set up Docker Buildx
155+ uses : docker/setup-buildx-action@v3
156+
157+ - name : Build and push by digest
158+ id : build
159+ uses : docker/build-push-action@v6
160+ with :
161+ context : .
162+ file : Dockerfile.pro
163+ pull : true
164+ platforms : ${{ matrix.docker_platform }}
165+ labels : |
166+ org.opencontainers.image.source=https://github.com/riverqueue/riverui
167+ org.opencontainers.image.description=River UI Pro is a web-based user interface for River, with pro features.
168+ org.opencontainers.image.licenses=MPL-2.0
169+ cache-from : type=registry,ref=riverqueue.com/riverproui:cache
170+ cache-to : type=registry,ref=riverqueue.com/riverproui:cache,mode=max
171+ outputs : type=image,name=riverqueue.com/riverproui,push-by-digest=true,name-canonical=true,push=false,annotation-index.org.opencontainers.image.description=River UI Pro
172+
173+ - name : Generate artifact attestation
174+ uses : actions/attest-build-provenance@v1
175+ with :
176+ push-to-registry : false
177+ subject-digest : ${{ steps.build.outputs.digest }}
178+ subject-name : riverqueue.com/riverproui
179+
180+ - name : Export digest
181+ run : |
182+ mkdir -p /tmp/digests
183+ digest="${{ steps.build.outputs.digest }}"
184+ touch "/tmp/digests/${digest#sha256:}"
185+
186+ - name : Upload digest
187+ uses : actions/upload-artifact@v4
188+ with :
189+ name : digests-pro-${{ env.PLATFORM_PAIR }}
190+ path : /tmp/digests/*
191+ if-no-files-found : error
192+ retention-days : 1
193+
194+ merge-riverui :
195+ name : " Merge Docker manifests: riverui"
196+ runs-on : ubuntu-latest
197+ needs :
198+ - build-riverui
199+ permissions :
200+ contents : read
201+ packages : write
119202
203+ steps :
120204 - name : Download digests
121205 uses : actions/download-artifact@v4
122206 with :
123207 path : /tmp/digests
124- pattern : digests-*
208+ pattern : digests-oss- *
125209 merge-multiple : true
126210
127211 - name : Set up Docker Buildx
@@ -138,7 +222,7 @@ jobs:
138222 id : meta
139223 uses : docker/metadata-action@v5
140224 with :
141- images : ghcr.io /${{ env.IMAGE_NAME }}
225+ images : ${{ env.REGISTRY }} /${{ env.IMAGE_NAME }}
142226 labels : |
143227 org.opencontainers.image.source=https://github.com/riverqueue/riverui
144228 org.opencontainers.image.description="River UI is a web-based user interface for River, a fast and reliable background job system."
@@ -148,13 +232,140 @@ jobs:
148232 type=ref,event=pr
149233 type=semver,pattern={{version}}
150234 type=semver,pattern={{major}}.{{minor}}
235+ type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
151236
152237 - name : Create manifest list and push
153238 working-directory : /tmp/digests
154239 run : |
155- docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
240+ docker buildx imagetools create \
241+ --annotation "index:org.opencontainers.image.source=https://github.com/riverqueue/riverui" \
242+ --annotation "index:org.opencontainers.image.description=River UI is a web-based user interface for River, a fast and reliable background job system." \
243+ --annotation "index:org.opencontainers.image.licenses=MPL-2.0" \
244+ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
156245 $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
157246
158- - name : Inspect image
247+ merge-riverproui :
248+ name : " Merge Docker manifests: riverproui"
249+ runs-on : ubuntu-latest
250+ needs :
251+ - build-riverproui
252+ outputs :
253+ image-name : ${{ steps.meta-pro.outputs.tags }}
254+ digest : ${{ steps.merge.outputs.digest }}
255+ permissions :
256+ attestations : write
257+ contents : read
258+ id-token : write
259+
260+ steps :
261+ - name : Download digests
262+ uses : actions/download-artifact@v4
263+ with :
264+ path : /tmp/digests
265+ pattern : digests-pro-*
266+ merge-multiple : true
267+
268+ - name : Set up Docker Buildx
269+ uses : docker/setup-buildx-action@v3
270+
271+ - name : Docker meta for Pro
272+ id : meta-pro
273+ uses : docker/metadata-action@v5
274+ with :
275+ images : riverqueue.com/riverproui
276+ labels : |
277+ org.opencontainers.image.source=https://github.com/riverqueue/riverui
278+ org.opencontainers.image.description="River UI Pro is a web-based user interface for River, with pro features."
279+ org.opencontainers.image.licenses=MPL-2.0
280+ # TODO: type=ref are temporary for testing
281+ tags : |
282+ type=ref,event=branch
283+ type=ref,event=pr
284+ type=semver,pattern={{version}}
285+ type=semver,pattern={{major}}.{{minor}}
286+ type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
287+
288+ - name : Create manifest list and export to OCI
289+ id : merge
290+ run : |
291+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
292+ $(printf 'riverqueue.com/riverproui@sha256:%s ' /tmp/digests/*)
293+ docker buildx imagetools create --dry-run $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
294+ $(printf 'riverqueue.com/riverproui@sha256:%s ' /tmp/digests/*) > /tmp/oci
295+ echo "digest=$(sha256sum /tmp/oci | cut -d' ' -f1)" >> $GITHUB_OUTPUT
296+
297+ - name : Upload OCI artifact
298+ uses : actions/upload-artifact@v4
299+ with :
300+ name : pro-oci
301+ path : /tmp/oci
302+ retention-days : 1
303+
304+ publish-riverproui :
305+ name : " Publish Docker image: riverproui"
306+ runs-on : ubuntu-latest
307+ needs :
308+ - merge-riverproui
309+
310+ steps :
311+ - name : Checkout
312+ uses : actions/checkout@v4
313+ with :
314+ ref : ${{ inputs.ref || github.ref }}
315+
316+ - name : Download OCI artifact
317+ uses : actions/download-artifact@v4
318+ with :
319+ name : pro-oci
320+ path : /tmp/oci
321+
322+ - name : Install jq
323+ run : sudo apt-get update && sudo apt-get install -y jq
324+
325+ - name : Install AWS CLI
326+ uses : unfor19/install-aws-cli-action@v1
327+ with :
328+ version : 2
329+ verbose : false
330+ arch : amd64
331+
332+ - name : Configure AWS CLI
333+ run : |
334+ aws configure set aws_access_key_id ${{ secrets.R2_ACCESS_KEY_ID }}
335+ aws configure set aws_secret_access_key ${{ secrets.R2_SECRET_ACCESS_KEY }}
336+ aws configure set default.region auto
337+
338+ - name : Sync blobs to R2
339+ # TODO: temporary for testing
340+ # if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
341+ env :
342+ DOCKER_RELEASE_STORAGE_BUCKET : ${{ vars.DOCKER_RELEASE_STORAGE_BUCKET }}
343+ REPO_NAME : riverqueue.com/riverproui
344+ ENDPOINT_URL : ${{ secrets.R2_ENDPOINT_URL }}
345+ run : |
346+ aws s3 cp /tmp/oci/blobs/sha256/ s3://$DOCKER_RELEASE_STORAGE_BUCKET/$REPO_NAME/blobs/sha256/ --recursive --endpoint-url $ENDPOINT_URL
347+
348+ - name : Upload manifest by digest
349+ # TODO: temporary for testing
350+ # if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
351+ env :
352+ DOCKER_RELEASE_STORAGE_BUCKET : ${{ vars.DOCKER_RELEASE_STORAGE_BUCKET }}
353+ REPO_NAME : riverqueue.com/riverproui
354+ ENDPOINT_URL : ${{ secrets.R2_ENDPOINT_URL }}
355+ run : |
356+ MANIFEST_DIGEST=$(sha256sum /tmp/oci/index.json | awk '{print $1}')
357+ aws s3 cp /tmp/oci/index.json s3://$DOCKER_RELEASE_STORAGE_BUCKET/$REPO_NAME/manifests/sha256:$MANIFEST_DIGEST --content-type "application/vnd.docker.distribution.manifest.list.v2+json" --endpoint-url $ENDPOINT_URL
358+
359+ - name : Upload tagged manifests
360+ # TODO: temporary for testing
361+ # if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
362+ env :
363+ DOCKER_RELEASE_STORAGE_BUCKET : ${{ vars.DOCKER_RELEASE_STORAGE_BUCKET }}
364+ REPO_NAME : riverqueue.com/riverproui
365+ ENDPOINT_URL : ${{ secrets.R2_ENDPOINT_URL }}
159366 run : |
160- docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
367+ # Extract tags from the merge job output
368+ TAGS=$(echo '${{ needs.merge-riverproui.outputs.image-name }}' | tr ' ' '\n' | sed 's|riverqueue.com/riverproui:||' | grep -v '^$')
369+ for TAG in $TAGS; do
370+ aws s3 cp /tmp/oci/index.json s3://$DOCKER_RELEASE_STORAGE_BUCKET/$REPO_NAME/manifests/$TAG --content-type "application/vnd.docker.distribution.manifest.list.v2+json" --endpoint-url $ENDPOINT_URL
371+ done
0 commit comments