Build Runner Images #36
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 Runner Images | |
| on: | |
| schedule: | |
| - cron: "0 3 * * 0" # Weekly on Sunday at 3 AM UTC - cleanup orphaned images | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Development version tag (must start with 'dev-', e.g., dev-test, dev-feature-x)" | |
| required: true | |
| default: "dev-" | |
| target: | |
| description: "Build target" | |
| required: true | |
| default: "all" | |
| type: choice | |
| options: | |
| - github-runner | |
| - gitea-runner | |
| - gitlab-runner | |
| - all | |
| push: | |
| tags: | |
| - "ubuntu-24.04-github/*" # Matches: ubuntu-24.04-github/2.322.0-1 | |
| - "ubuntu-24.04-gitea/*" # Matches: ubuntu-24.04-gitea/0.2.11-1 | |
| - "ubuntu-24.04-gitlab/*" # Matches: ubuntu-24.04-gitlab/18.7.0-1 | |
| branches: | |
| - main | |
| paths: | |
| - "images/docker/ubuntu-24.04/**" | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_BASE: ${{ github.repository_owner }}/fireactions-images | |
| jobs: | |
| # Determine what to build based on trigger and changed paths | |
| prepare: | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'schedule' | |
| outputs: | |
| targets: ${{ steps.set-targets.outputs.targets }} | |
| version: ${{ steps.set-targets.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Detect changed paths | |
| if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/') | |
| uses: dorny/paths-filter@v3 | |
| id: changes | |
| with: | |
| filters: | | |
| shared: | |
| - 'images/docker/ubuntu-24.04/Dockerfile' | |
| - 'images/docker/ubuntu-24.04/overlay/common/**' | |
| github: | |
| - 'images/docker/ubuntu-24.04/overlay/github/**' | |
| gitea: | |
| - 'images/docker/ubuntu-24.04/overlay/gitea/**' | |
| gitlab: | |
| - 'images/docker/ubuntu-24.04/overlay/gitlab/**' | |
| docs_only: | |
| - 'images/docker/ubuntu-24.04/*.md' | |
| - name: Validate manual dispatch version | |
| if: github.event_name == 'workflow_dispatch' | |
| run: | | |
| VERSION="${{ github.event.inputs.version }}" | |
| # Enforce 'dev-*' pattern for manual dispatch | |
| if [[ ! "${VERSION}" =~ ^dev-.+ ]]; then | |
| echo "::error::Manual dispatch versions must start with 'dev-' (e.g., dev-test, dev-feature-x). Release versions should use git tags." | |
| exit 1 | |
| fi | |
| echo "✅ Version '${VERSION}' is valid (dev build)" | |
| - name: Determine build targets | |
| id: set-targets | |
| run: | | |
| # Determine which targets to build | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| # Manual dispatch - use user selection | |
| VERSION="${{ github.event.inputs.version }}" | |
| TARGET="${{ github.event.inputs.target }}" | |
| elif [[ "${{ github.ref }}" == refs/tags/ubuntu-24.04-github/* ]]; then | |
| TAG="${GITHUB_REF#refs/tags/}" | |
| VERSION="${TAG#ubuntu-24.04-github/}" | |
| TARGET="github-runner" | |
| elif [[ "${{ github.ref }}" == refs/tags/ubuntu-24.04-gitea/* ]]; then | |
| TAG="${GITHUB_REF#refs/tags/}" | |
| VERSION="${TAG#ubuntu-24.04-gitea/}" | |
| TARGET="gitea-runner" | |
| elif [[ "${{ github.ref }}" == refs/tags/ubuntu-24.04-gitlab/* ]]; then | |
| TAG="${GITHUB_REF#refs/tags/}" | |
| VERSION="${TAG#ubuntu-24.04-gitlab/}" | |
| TARGET="gitlab-runner" | |
| else | |
| # Push to main - smart detection based on changed paths | |
| VERSION="latest" | |
| if [[ "${{ steps.changes.outputs.shared }}" == "true" ]]; then | |
| # Shared files changed (Dockerfile or common overlay) - build all | |
| echo "📦 Shared files changed - building all targets" | |
| TARGET="all" | |
| elif [[ "${{ steps.changes.outputs.docs_only }}" == "true" && \ | |
| "${{ steps.changes.outputs.github }}" != "true" && \ | |
| "${{ steps.changes.outputs.gitea }}" != "true" && \ | |
| "${{ steps.changes.outputs.gitlab }}" != "true" ]]; then | |
| # Only docs changed - skip builds | |
| echo "📝 Only documentation changed - skipping builds" | |
| TARGET="none" | |
| else | |
| # Build only changed targets | |
| TARGETS_ARRAY=() | |
| [[ "${{ steps.changes.outputs.github }}" == "true" ]] && TARGETS_ARRAY+=("github-runner") | |
| [[ "${{ steps.changes.outputs.gitea }}" == "true" ]] && TARGETS_ARRAY+=("gitea-runner") | |
| [[ "${{ steps.changes.outputs.gitlab }}" == "true" ]] && TARGETS_ARRAY+=("gitlab-runner") | |
| if [[ ${#TARGETS_ARRAY[@]} -eq 0 ]]; then | |
| echo "🤷 No relevant changes detected - skipping builds" | |
| TARGET="none" | |
| else | |
| echo "🎯 Building changed targets: ${TARGETS_ARRAY[*]}" | |
| # Build JSON array directly | |
| printf -v TARGETS_JSON '"%s",' "${TARGETS_ARRAY[@]}" | |
| TARGETS="[${TARGETS_JSON%,}]" | |
| echo "targets=${TARGETS}" >> $GITHUB_OUTPUT | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| fi | |
| fi | |
| # Build JSON array of targets | |
| if [[ "${TARGET}" == "none" ]]; then | |
| TARGETS='[]' | |
| elif [[ "${TARGET}" == "all" ]]; then | |
| TARGETS='["github-runner","gitea-runner","gitlab-runner"]' | |
| else | |
| TARGETS='["'"${TARGET}"'"]' | |
| fi | |
| echo "targets=${TARGETS}" >> $GITHUB_OUTPUT | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| echo "Building targets: ${TARGETS} with version: ${VERSION}" | |
| # Build each target image on native runners for each architecture | |
| build: | |
| needs: prepare | |
| if: needs.prepare.outputs.targets != '[]' | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: ${{ fromJson(needs.prepare.outputs.targets) }} | |
| arch: | |
| - amd64 | |
| - arm64 | |
| include: | |
| - arch: amd64 | |
| runner: ubuntu-latest | |
| platform: linux/amd64 | |
| - arch: arm64 | |
| runner: ubuntu-24.04-arm | |
| platform: linux/arm64 | |
| steps: | |
| - name: Set variables | |
| id: vars | |
| run: | | |
| VERSION="${{ needs.prepare.outputs.version }}" | |
| TARGET="${{ matrix.target }}" | |
| # Map target to image name: github-runner -> ubuntu-24.04-github | |
| case "${TARGET}" in | |
| github-runner) IMAGE_SUFFIX="ubuntu-24.04-github" ;; | |
| gitea-runner) IMAGE_SUFFIX="ubuntu-24.04-gitea" ;; | |
| gitlab-runner) IMAGE_SUFFIX="ubuntu-24.04-gitlab" ;; | |
| esac | |
| IMAGE_NAME="${{ env.REGISTRY }}/${{ env.IMAGE_BASE }}/${IMAGE_SUFFIX}" | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT | |
| echo "image_suffix=${IMAGE_SUFFIX}" >> $GITHUB_OUTPUT | |
| echo "target=${TARGET}" >> $GITHUB_OUTPUT | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push by digest | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: images/docker/ubuntu-24.04 | |
| target: ${{ matrix.target }} | |
| platforms: ${{ matrix.platform }} | |
| push: true | |
| pull: true # Always pull fresh base images (fireteact:latest, fireactions, ubuntu) | |
| outputs: type=image,name=${{ steps.vars.outputs.image_name }},push-by-digest=true,name-canonical=true | |
| cache-from: type=gha,scope=${{ matrix.target }}-${{ matrix.arch }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.target }}-${{ matrix.arch }} | |
| labels: | | |
| org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.version=${{ steps.vars.outputs.version }} | |
| org.opencontainers.image.title=${{ steps.vars.outputs.image_suffix }} | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-${{ matrix.target }}-${{ matrix.arch }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| # Create multi-arch manifests for each target | |
| manifest: | |
| needs: [prepare, build] | |
| if: needs.prepare.outputs.targets != '[]' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| matrix: | |
| target: ${{ fromJson(needs.prepare.outputs.targets) }} | |
| steps: | |
| - name: Set variables | |
| id: vars | |
| run: | | |
| VERSION="${{ needs.prepare.outputs.version }}" | |
| TARGET="${{ matrix.target }}" | |
| # Map target to image name: github-runner -> ubuntu-24.04-github | |
| case "${TARGET}" in | |
| github-runner) IMAGE_SUFFIX="ubuntu-24.04-github" ;; | |
| gitea-runner) IMAGE_SUFFIX="ubuntu-24.04-gitea" ;; | |
| gitlab-runner) IMAGE_SUFFIX="ubuntu-24.04-gitlab" ;; | |
| esac | |
| IMAGE_NAME="${{ env.REGISTRY }}/${{ env.IMAGE_BASE }}/${IMAGE_SUFFIX}" | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT | |
| echo "image_suffix=${IMAGE_SUFFIX}" >> $GITHUB_OUTPUT | |
| echo "target=${TARGET}" >> $GITHUB_OUTPUT | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/digests | |
| pattern: digests-${{ matrix.target }}-* | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create manifest list and push | |
| working-directory: /tmp/digests | |
| run: | | |
| IMAGE_NAME="${{ steps.vars.outputs.image_name }}" | |
| VERSION="${{ steps.vars.outputs.version }}" | |
| # Build tags array | |
| TAGS="-t ${IMAGE_NAME}:${VERSION}" | |
| if [[ "${VERSION}" != "latest" ]]; then | |
| TAGS="${TAGS} -t ${IMAGE_NAME}:latest" | |
| fi | |
| # Create and push manifest | |
| docker buildx imagetools create ${TAGS} \ | |
| $(printf "${IMAGE_NAME}@sha256:%s " *) | |
| - name: Inspect image | |
| run: | | |
| docker buildx imagetools inspect ${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.version }} | |
| - name: Summary | |
| run: | | |
| echo "## ${{ steps.vars.outputs.image_suffix }} Image Published" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Image:** \`${{ steps.vars.outputs.image_name }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tags:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`${{ steps.vars.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ steps.vars.outputs.version }}" != "latest" ]]; then | |
| echo "- \`latest\`" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Architectures:** \`amd64\`, \`arm64\`" >> $GITHUB_STEP_SUMMARY | |
| # Cleanup orphaned images (runs weekly on schedule) | |
| cleanup: | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'schedule' | |
| permissions: | |
| packages: write | |
| strategy: | |
| matrix: | |
| package: | |
| - fireactions-images/ubuntu-24.04-github | |
| - fireactions-images/ubuntu-24.04-gitea | |
| - fireactions-images/ubuntu-24.04-gitlab | |
| steps: | |
| - name: Delete orphaned images | |
| uses: dataaxiom/ghcr-cleanup-action@v1 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| package: ${{ matrix.package }} | |
| delete-untagged: true | |
| delete-ghost-images: true | |
| delete-partial-images: true | |
| older-than: 3 days | |
| keep-n-tagged: 5 | |
| - name: Summary | |
| run: | | |
| echo "## GHCR Cleanup Completed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Removed orphaned/untagged images from \`${{ env.REGISTRY }}/${{ matrix.package }}\`" >> $GITHUB_STEP_SUMMARY |