Build, scan, and publish #17
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: | |
| # Security: prevent fork PRs from running on self-hosted runners. | |
| # The build job uses a self-hosted runner and must only run for | |
| # pushes, schedules, workflow_dispatch, or PRs from this repo. | |
| # Fork PRs are restricted to the lint job (GitHub-hosted runner). | |
| permissions: | |
| contents: read | |
| packages: write | |
| security-events: write | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| # ------------------------------------------------------------------- | |
| # Lint — hadolint Dockerfile linting (lightweight, stays on GitHub-hosted) | |
| # ------------------------------------------------------------------- | |
| 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 on self-hosted Mac Mini, push to GHCR | |
| # ------------------------------------------------------------------- | |
| build: | |
| runs-on: [self-hosted, mac-mini] | |
| needs: lint | |
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | |
| 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/arm64' }} | |
| 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: [self-hosted, mac-mini] | |
| needs: build | |
| if: github.event_name != 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@v0.35.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: [self-hosted, mac-mini] | |
| 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: [self-hosted, mac-mini] | |
| 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" |