Skip to content

Commit 8856e58

Browse files
committed
docker publishing pipeline for riverproui
1 parent d947a54 commit 8856e58

3 files changed

Lines changed: 271 additions & 25 deletions

File tree

.github/workflows/docker.yaml

Lines changed: 233 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,27 @@ on:
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

1318
env:
1419
IMAGE_NAME: ${{ github.repository }}
1520
REGISTRY: ghcr.io
1621

1722
jobs:
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

@@ -59,7 +64,7 @@ jobs:
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."
@@ -69,6 +74,7 @@ jobs:
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

Dockerfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ COPY *.go internal docs/README.md LICENSE ./
2121
COPY cmd/ cmd/
2222
COPY internal/ internal/
2323
COPY public/ public/
24+
COPY riverproui/ riverproui/
2425
COPY --from=build-ui /app/dist ./dist
2526

26-
RUN go build -o /bin/riverui ./cmd/riverui
27+
ARG BINARY=riverui
28+
RUN go build -o /bin/$BINARY ./cmd/$BINARY
2729

2830
FROM alpine:3.22.0
2931
ENV PATH_PREFIX="/"
30-
COPY --from=build-go /bin/riverui /bin/riverui
31-
CMD ["/bin/sh", "-c", "/bin/riverui -prefix=$PATH_PREFIX"]
32+
COPY --from=build-go /bin/$BINARY /bin/$BINARY
33+
CMD ["/bin/sh", "-c", "/bin/$BINARY -prefix=$PATH_PREFIX"]

Dockerfile.pro

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# syntax=docker/dockerfile:1
2+
3+
FROM node:22-alpine AS build-ui
4+
WORKDIR /app
5+
COPY package.json package-lock.json ./
6+
RUN npm install
7+
ENV NODE_ENV=production
8+
COPY . .
9+
10+
RUN npx vite build
11+
12+
# Build the Go binary, including embedded UI files:
13+
FROM golang:1.24-alpine AS build-go
14+
WORKDIR /go/src/riverui
15+
16+
COPY go.mod go.sum ./
17+
RUN go mod download
18+
19+
# Copy Go files without copying the ui dir:
20+
COPY *.go internal docs/README.md LICENSE ./
21+
COPY cmd/ cmd/
22+
COPY internal/ internal/
23+
COPY public/ public/
24+
COPY riverproui/ riverproui/
25+
COPY --from=build-ui /app/dist ./dist
26+
27+
WORKDIR /go/src/riverui/riverproui
28+
RUN go build -o /bin/riverproui ./cmd/riverproui
29+
30+
FROM alpine:3.22.0
31+
ENV PATH_PREFIX="/"
32+
COPY --from=build-go /bin/riverproui /bin/riverproui
33+
CMD ["/bin/sh", "-c", "/bin/riverproui -prefix=$PATH_PREFIX"]

0 commit comments

Comments
 (0)