Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
name: Build & Publish Docker Image

on:
push:
branches: [main, develop]
tags: ["v*.*.*"]
pull_request:
branches: [main, develop]

permissions:
contents: read

# PR builds move the mutable :pr-<N> tag on every push — cancel superseded
# runs so a retest always reflects the latest commit. Never cancel a
# push-to-develop/main build (those promote real tags).
concurrency:
group: docker-publish-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
IMAGE_NAME: evoapicloud/evo-flow-community

jobs:
# ---------------------------------------------------------------------------
# PR builds: single-arch amd64 (review env is one machine — half the cost of
# multi-arch), pushed to the ephemeral :pr-<N> (mutable, for the reviewer) and
# :sha-<sha7> (immutable, from the PR head, for audit). See
# docs/pr-review-testing-workflow.md §6.1.
# ---------------------------------------------------------------------------
build-pr:
if: github.event_name == 'pull_request'
name: Build PR image (amd64)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 1

- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Resolve PR tags
id: tags
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
IMAGE="${{ env.IMAGE_NAME }}"
SHA_SHORT="${HEAD_SHA:0:7}"
echo "tags=${IMAGE}:pr-${PR_NUMBER},${IMAGE}:sha-${SHA_SHORT}" >> "$GITHUB_OUTPUT"

- name: Build and push (amd64)
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.tags.outputs.tags }}
# Shares the amd64 scope with the branch build so PRs warm off
# develop's cache. GHA ref-scoping keeps PR writes isolated from base.
cache-from: type=gha,scope=linux/amd64
cache-to: type=gha,scope=linux/amd64,mode=max

# ---------------------------------------------------------------------------
# Branch/tag builds: multi-arch (amd64 + arm64) by digest, merged into a
# manifest and pushed to the promoted tags (:develop, :latest, :vX.Y.Z).
# ---------------------------------------------------------------------------
build:
if: github.event_name != 'pull_request'
name: Build ${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 1

- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with:
context: .
file: ./Dockerfile
platforms: ${{ matrix.platform }}
push: true
provenance: mode=max
sbom: true
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,scope=${{ matrix.platform }},mode=max

- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: digest-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
retention-days: 1

merge:
if: github.event_name != 'pull_request'
name: Merge Manifests & Tag
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: digest-*
path: /tmp/digests
merge-multiple: true

- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Resolve tags
id: tags
env:
GH_REF: ${{ github.ref }}
GH_REF_NAME: ${{ github.ref_name }}
GH_SHA: ${{ github.sha }}
run: |
IMAGE="${{ env.IMAGE_NAME }}"
SHA_SHORT="${GH_SHA:0:7}"
TAGS=""

if [[ "${GH_REF}" == refs/tags/v* ]]; then
VERSION="${GH_REF_NAME#v}"
TAGS="${IMAGE}:${VERSION},${IMAGE}:latest"
elif [[ "${GH_REF}" == refs/heads/main ]]; then
TAGS="${IMAGE}:latest,${IMAGE}:main-${SHA_SHORT}"
elif [[ "${GH_REF}" == refs/heads/develop ]]; then
TAGS="${IMAGE}:develop,${IMAGE}:develop-${SHA_SHORT}"
fi

echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"

- name: Create and push manifest
working-directory: /tmp/digests
env:
MERGE_TAGS: ${{ steps.tags.outputs.tags }}
run: |
IFS=',' read -ra TAG_ARRAY <<< "${MERGE_TAGS}"
TAG_ARGS=""
for tag in "${TAG_ARRAY[@]}"; do
TAG_ARGS="${TAG_ARGS} -t ${tag}"
done

docker buildx imagetools create ${TAG_ARGS} \
$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)
Loading