Skip to content

Build, scan, and publish #17

Build, scan, and publish

Build, scan, and publish #17

Workflow file for this run

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"