Build, scan, and publish #8
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, scan, and publish | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| branches: [main] | |
| schedule: | |
| - cron: "0 6 * * 1" # weekly Monday 06:00 UTC | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| packages: write | |
| security-events: write | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| # ------------------------------------------------------------------- | |
| # Lint — hadolint Dockerfile linting | |
| # ------------------------------------------------------------------- | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Lint Dockerfile | |
| uses: hadolint/hadolint-action@v3.1.0 | |
| with: | |
| dockerfile: Dockerfile | |
| # ------------------------------------------------------------------- | |
| # Build — multi-platform image, push to GHCR | |
| # ------------------------------------------------------------------- | |
| build: | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| outputs: | |
| digest: ${{ steps.build-push.outputs.digest }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - 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 GHCR | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata for tags and labels | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=raw,value={{date 'YYYYMMDD'}},enable={{is_default_branch}} | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| type=sha,prefix= | |
| - name: Build and push | |
| id: build-push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| # Multi-platform on push; single platform on PR (--load does not | |
| # support manifest lists, so we build only the runner's native arch) | |
| platforms: ${{ github.event_name != 'pull_request' && 'linux/amd64,linux/arm64' || 'linux/amd64' }} | |
| push: ${{ github.event_name != 'pull_request' }} | |
| load: ${{ github.event_name == 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # ------------------------------------------------------------------- | |
| # Scan — Trivy vulnerability scanning with SARIF upload | |
| # ------------------------------------------------------------------- | |
| scan: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event_name != 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@0.28.0 | |
| with: | |
| image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.digest }} | |
| format: sarif | |
| output: trivy-results.sarif | |
| severity: CRITICAL,HIGH | |
| - name: Upload Trivy SARIF to GitHub Security | |
| uses: github/codeql-action/upload-sarif@v3 | |
| with: | |
| sarif_file: trivy-results.sarif | |
| # ------------------------------------------------------------------- | |
| # Test — run validate-tools.sh inside the built image | |
| # ------------------------------------------------------------------- | |
| test: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event_name != 'pull_request' | |
| steps: | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Pull and validate tools | |
| run: | | |
| docker pull "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.digest }}" | |
| docker run --rm "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.digest }}" \ | |
| /usr/local/bin/validate-tools.sh | |
| # ------------------------------------------------------------------- | |
| # Size report — output image size to step summary | |
| # ------------------------------------------------------------------- | |
| size-report: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event_name != 'pull_request' | |
| steps: | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Report image size | |
| run: | | |
| docker pull "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.digest }}" | |
| SIZE=$(docker image inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.digest }}" --format '{{.Size}}') | |
| SIZE_MB=$((SIZE / 1024 / 1024)) | |
| echo "## Image Size Report" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Metric | Value |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Raw size | ${SIZE_MB} MB |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Digest | \`${{ needs.build.outputs.digest }}\` |" >> "$GITHUB_STEP_SUMMARY" |