Skip to content

Fix ARM64 build timeout by using native ARM64 runner #19

Fix ARM64 build timeout by using native ARM64 runner

Fix ARM64 build timeout by using native ARM64 runner #19

Workflow file for this run

name: Build and Release Docker Image
on:
push:
branches:
- main
paths:
- 'Dockerfile'
- 'scripts/**'
- '.github/workflows/release.yml'
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
force_build:
description: 'Force Docker image build'
required: false
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# === DETECT CHANGES ===
detect-changes:
runs-on: ubuntu-latest
outputs:
docker-changed: ${{ steps.changes.outputs.docker }}
scripts-changed: ${{ steps.changes.outputs.scripts }}
workflow-changed: ${{ steps.changes.outputs.workflow }}
should-build: ${{ steps.should-build.outputs.result }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Detect changes
id: changes
run: |
# For push events, compare with previous commit
# For PR events, compare with base branch
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
else
BASE_SHA="${{ github.event.before }}"
fi
# Get changed files
CHANGED_FILES=$(git diff --name-only "$BASE_SHA" HEAD 2>/dev/null || git diff --name-only HEAD~1 HEAD)
# Check for Docker-related changes
if echo "$CHANGED_FILES" | grep -qE '^Dockerfile$'; then
echo "docker=true" >> $GITHUB_OUTPUT
else
echo "docker=false" >> $GITHUB_OUTPUT
fi
# Check for scripts changes
if echo "$CHANGED_FILES" | grep -qE '^scripts/'; then
echo "scripts=true" >> $GITHUB_OUTPUT
else
echo "scripts=false" >> $GITHUB_OUTPUT
fi
# Check for workflow changes
if echo "$CHANGED_FILES" | grep -qE '^\.github/workflows/'; then
echo "workflow=true" >> $GITHUB_OUTPUT
else
echo "workflow=false" >> $GITHUB_OUTPUT
fi
- name: Determine if build is needed
id: should-build
run: |
if [ "${{ github.event.inputs.force_build }}" = "true" ]; then
echo "result=true" >> $GITHUB_OUTPUT
elif [ "${{ steps.changes.outputs.docker }}" = "true" ] || [ "${{ steps.changes.outputs.scripts }}" = "true" ]; then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
# === BUILD AND TEST DOCKER IMAGE (PR) ===
docker-build-test:
runs-on: ubuntu-latest
needs: [detect-changes]
if: github.event_name == 'pull_request' && needs.detect-changes.outputs.should-build == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image (test)
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Test Docker image
run: |
echo "Building image for testing..."
docker build -t sandbox-test .
echo ""
echo "=== Testing installed tools ==="
echo "Note: Using entrypoint script which initializes all environments"
echo ""
echo "Testing Node.js..."
docker run --rm sandbox-test node --version || echo "Node.js test failed"
echo ""
echo "Testing Python..."
docker run --rm sandbox-test python --version || echo "Python test failed"
echo ""
echo "Testing Go..."
docker run --rm sandbox-test go version || echo "Go test failed"
echo ""
echo "Testing Rust..."
docker run --rm sandbox-test rustc --version || echo "Rust test failed"
echo ""
echo "Testing Java..."
docker run --rm sandbox-test java -version || echo "Java test failed"
echo ""
echo "Testing Bun..."
docker run --rm sandbox-test bun --version || echo "Bun test failed"
echo ""
echo "Testing Deno..."
docker run --rm sandbox-test deno --version || echo "Deno test failed"
echo ""
echo "Testing GitHub CLI..."
docker run --rm sandbox-test gh --version || echo "GitHub CLI test failed"
echo ""
echo "Testing Lean..."
docker run --rm sandbox-test lean --version || echo "Lean test failed"
echo ""
echo "Testing Perl..."
docker run --rm sandbox-test perl --version || echo "Perl test failed"
echo ""
echo "Testing PHP..."
docker run --rm sandbox-test php --version || echo "PHP test failed"
echo ""
echo "=== All tests completed ==="
# === BUILD AND PUSH DOCKER IMAGE (MAIN) ===
docker-build-push:
runs-on: ubuntu-latest
needs: [detect-changes]
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true'
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
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 GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest
type=sha,prefix=
type=raw,value={{date 'YYYYMMDD'}}
- name: Build and push Docker image (amd64)
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# === BUILD AND PUSH ARM64 IMAGE ===
# Using native ARM64 runner to avoid QEMU emulation performance issues
# See: docs/case-studies/issue-7/README.md for analysis of the previous timeout
# Reference: https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview/
docker-build-push-arm64:
runs-on: ubuntu-24.04-arm # Native ARM64 runner (free for public repos since Jan 2025)
timeout-minutes: 120 # Safety timeout to prevent runaway builds
needs: [detect-changes, docker-build-push]
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true'
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Note: QEMU setup is not needed on native ARM64 runner
# Removing this step eliminates the 10-30x performance penalty from emulation
# - 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 GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
suffix=-arm64
tags: |
type=raw,value=latest
type=sha,prefix=
type=raw,value={{date 'YYYYMMDD'}}
- name: Build and push Docker image (arm64)
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# === CREATE MULTI-ARCH MANIFEST ===
docker-manifest:
runs-on: ubuntu-latest
needs: [docker-build-push, docker-build-push-arm64]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push multi-arch manifest
run: |
# Create manifest for latest tag
docker manifest create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
--amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
--amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-arm64
docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
echo "Multi-arch manifest created and pushed successfully"