Skip to content

Build and Push Docker Image #17

Build and Push Docker Image

Build and Push Docker Image #17

name: Build and Push Docker Image
on:
workflow_run:
workflows: ["Detect Release from CHANGELOG"]
types:
- completed
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to build (e.g., v0.6.12)'
required: true
type: string
security_scan:
description: 'Run security scan after build'
required: false
type: boolean
default: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
outputs:
run_security_scan: ${{ steps.set_scan_flag.outputs.run_security_scan }}
environment: prod
timeout-minutes: 120
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get tag from workflow_run or current event
id: get_tag
run: |
if [ "${{ github.event_name }}" == "workflow_run" ]; then
# Get the tag that was created in the detect-release workflow
echo "Triggered by workflow_run, fetching tag..."
TAG=$(git -c 'versionsort.suffix=-alpha' -c 'versionsort.suffix=-beta' \
ls-remote --tags --sort=-v:refname origin \
| grep 'refs/tags/v' \
| head -1 \
| awk '{print $2}' \
| sed 's/refs\/tags\///' \
| tr -d '{}^')
echo "TAG=$TAG" >> $GITHUB_OUTPUT
echo "REF_NAME=$TAG" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
# Manual workflow dispatch with tag input
TAG="${{ github.event.inputs.tag }}"
echo "TAG=$TAG" >> $GITHUB_OUTPUT
echo "REF_NAME=$TAG" >> $GITHUB_OUTPUT
else
# Normal tag push event
TAG="${{ github.ref_name }}"
echo "TAG=$TAG" >> $GITHUB_OUTPUT
echo "REF_NAME=$TAG" >> $GITHUB_OUTPUT
fi
# Strip 'v' prefix for Docker image tags (Git: v0.6.15 → Docker: 0.6.15)
VERSION_NO_V=${TAG#v}
echo "VERSION_NO_V=$VERSION_NO_V" >> $GITHUB_OUTPUT
echo "Using tag: $TAG (Docker version: $VERSION_NO_V)"
- name: Set security scan flag
id: set_scan_flag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
# Use the input value from manual trigger
RUN_SCAN="${{ github.event.inputs.security_scan == 'true' }}"
else
# Always run scan for automated builds
RUN_SCAN="true"
fi
echo "run_security_scan=$RUN_SCAN" >> $GITHUB_OUTPUT
echo "Security scan enabled: $RUN_SCAN"
- name: Determine if stable release
id: stable
run: |
TAG="${{ steps.get_tag.outputs.TAG }}"
TAG_NAME="${{ steps.get_tag.outputs.REF_NAME }}"
# Check if tag matches vX.X.X (stable) or vX.X.X-* (pre-release)
# Stable: v1.0.0, v2.0.1, v10.20.30
# Pre-release: v1.0.0-alpha, v1.0.0-pre1, v1.0.0-beta.1
if [[ $TAG_NAME =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "✅ Stable release: $TAG_NAME"
echo "IS_STABLE=true" >> $GITHUB_OUTPUT
else
echo "⏭️ Pre-release: $TAG_NAME"
echo "IS_STABLE=false" >> $GITHUB_OUTPUT
fi
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (Stable Release)
if: steps.stable.outputs.IS_STABLE == 'true'
id: meta_stable
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}},value=${{ steps.get_tag.outputs.TAG }}
type=semver,pattern={{major}}.{{minor}},value=${{ steps.get_tag.outputs.TAG }}
type=semver,pattern={{major}},value=${{ steps.get_tag.outputs.TAG }}
type=raw,value=latest
- name: Extract metadata (Pre-Release)
if: steps.stable.outputs.IS_STABLE == 'false'
id: meta_prerelease
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}},value=${{ steps.get_tag.outputs.TAG }}
- name: Build and push (Stable)
if: steps.stable.outputs.IS_STABLE == 'true'
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta_stable.outputs.tags }}
labels: ${{ steps.meta_stable.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true
sbom: true
- name: Build and push (Pre-Release)
if: steps.stable.outputs.IS_STABLE == 'false'
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta_prerelease.outputs.tags }}
labels: ${{ steps.meta_prerelease.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true
sbom: true
- name: Wait for registry propagation
if: github.event.inputs.security_scan != 'false'
run: |
echo "⏳ Waiting 90 seconds for image to propagate in ghcr.io registry..."
echo "This ensures security scans can pull the image successfully"
sleep 90
echo "✅ Registry propagation wait complete"
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.get_tag.outputs.VERSION_NO_V }}
format: 'sarif'
output: 'trivy-results.sarif'
scan-type: 'image'
severity: 'CRITICAL,HIGH'
continue-on-error: true
timeout-minutes: 15
- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: hashFiles('trivy-results.sarif') != ''
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy'
timeout-minutes: 5
- name: Run Grype vulnerability scanner
uses: anchore/scan-action@v4
id: grype
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.get_tag.outputs.VERSION_NO_V }}
output-format: 'sarif'
fail-build: false
severity-cutoff: 'high'
continue-on-error: true
timeout-minutes: 15
- name: Upload Grype results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: hashFiles('grype-results.sarif') != ''
with:
sarif_file: 'grype-results.sarif'
category: 'grype'
timeout-minutes: 5
- name: Job Summary
run: |
echo "## Docker Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.stable.outputs.IS_STABLE }}" == "true" ]; then
echo "✅ **Stable Release Built**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Published Tags:" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta_stable.outputs.tags }}" | tr ',' '\n' | sed 's/^/- `/' | sed 's/$/`/' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note:** \`latest\` tag is included for stable releases" >> $GITHUB_STEP_SUMMARY
else
echo "⏭️ **Pre-Release Built**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Published Tags:" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta_prerelease.outputs.tags }}" | tr ',' '\n' | sed 's/^/- `/' | sed 's/$/`/' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note:** \`latest\` tag is NOT included for pre-releases" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🏗️ Built Platforms:" >> $GITHUB_STEP_SUMMARY
echo "- \`linux/amd64\` (Intel/AMD x86_64)" >> $GITHUB_STEP_SUMMARY
echo "- \`linux/arm64\` (Apple Silicon, ARM cloud)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔒 Security Scanning:" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Trivy vulnerability scan completed" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Grype vulnerability scan completed" >> $GITHUB_STEP_SUMMARY
echo "- 📊 Results available in **Security** tab" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Pull commands:" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.get_tag.outputs.REF_NAME }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY