Skip to content

fix: strip v prefix from Docker image tags on release (#242) #320

fix: strip v prefix from Docker image tags on release (#242)

fix: strip v prefix from Docker image tags on release (#242) #320

name: Docker Build & Publish
on:
push:
branches: ["main"]
tags: ["v*"]
pull_request:
branches: ["main"]
workflow_dispatch:
inputs:
version:
description: "Emergency: version tag to publish (e.g. v1.0.0-alpha.9)"
required: true
type: string
permissions:
contents: read
concurrency:
group: docker-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
jobs:
build-and-push:
name: Build & Push
# Tags are released by promoting the already-built build image — no rebuild needed.
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.push.outputs.digest }}
permissions:
contents: read
packages: write
id-token: write
attestations: write
artifact-metadata: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # ratchet:docker/setup-buildx-action@v4
- name: Set image name
run: echo "IMAGE=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # ratchet:docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6
with:
images: ${{ env.IMAGE }}
tags: |
type=ref,event=pr
type=raw,value=edge,enable={{is_default_branch}}
type=sha,prefix=build-,format=short,enable={{is_default_branch}}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
- name: Build and push
id: push
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # ratchet:docker/build-push-action@v7
with:
context: .
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: mode=max
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Attest image
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # ratchet:actions/attest-build-provenance@v4
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
scan:
name: Container Scan
needs: build-and-push
runs-on: ubuntu-latest
permissions:
contents: read
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
env:
GRYPE_VERSION: "0.112.0"
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6
- name: Set image name
run: echo "IMAGE=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Install grype
run: |
cd /tmp
curl -sSfL -o "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" \
"https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz"
curl -sSfL -o "grype_${GRYPE_VERSION}_checksums.txt" \
"https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_checksums.txt"
grep "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" "grype_${GRYPE_VERSION}_checksums.txt" | sha256sum --check
tar -xzf "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" -C /usr/local/bin grype
- name: Scan image
run: |
grype "${IMAGE}@${{ needs.build-and-push.outputs.digest }}" \
--config .grype.yaml \
-o "template=grype-report.txt" \
-o "json=grype-report.json"
- name: Upload scan reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # ratchet:actions/upload-artifact@v7
with:
name: grype-container-scan
path: |
grype-report.txt
grype-report.json
retention-days: 30
publish-release:
name: Publish Release
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # ratchet:docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # ratchet:docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set image name
run: echo "IMAGE=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Install grype
env:
GRYPE_VERSION: "0.112.0"
run: |
cd /tmp
curl -sSfL -o "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" \
"https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz"
curl -sSfL -o "grype_${GRYPE_VERSION}_checksums.txt" \
"https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_checksums.txt"
grep "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" "grype_${GRYPE_VERSION}_checksums.txt" | sha256sum --check
tar -xzf "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" -C /usr/local/bin grype
# Resolve the digest of the build-{sha} image produced when this commit landed on main.
# The tag ruleset enforces Container Scan passed on this commit before the tag could be
# pushed, so the build image is guaranteed scanned. Using the commit-pinned build tag
# (not :edge) ensures we promote the exact image for this release regardless of how
# many commits have landed on main since.
- name: Resolve build image digest
id: build
run: |
TAG="build-${GITHUB_SHA::7}"
DIGEST=$(docker buildx imagetools inspect \
"${IMAGE}:${TAG}" \
--format '{{.Manifest.Digest}}')
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "Promoting ${TAG} @ ${DIGEST}"
- name: Scan build image
run: |
grype "${IMAGE}@${{ steps.build.outputs.digest }}" \
--config .grype.yaml \
-o "template=grype-report.txt" \
-o "json=grype-report.json"
- name: Upload scan reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # ratchet:actions/upload-artifact@v7
with:
name: grype-release-scan
path: |
grype-report.txt
grype-report.json
retention-days: 90
- name: Promote build image to release tags
run: |
VERSION=${GITHUB_REF_NAME#v}
SOURCE="${IMAGE}@${{ steps.build.outputs.digest }}"
if [[ "${VERSION}" == *-* ]]; then
# Pre-release (contains -): push the explicit version tag only.
# Major/minor/latest convenience tags must not point at a pre-release.
echo "Pre-release detected (${VERSION}) — skipping major/minor/latest tags"
docker buildx imagetools create \
--tag "${IMAGE}:${VERSION}" \
${SOURCE}
else
# Stable release: push all convenience tags.
MAJOR=$(echo ${VERSION} | cut -d. -f1)
MINOR=$(echo ${VERSION} | cut -d. -f1-2)
docker buildx imagetools create \
--tag "${IMAGE}:${VERSION}" \
--tag "${IMAGE}:${MINOR}" \
--tag "${IMAGE}:${MAJOR}" \
--tag "${IMAGE}:latest" \
${SOURCE}
fi