Build and Push Docker Image #16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |