name: '๐Ÿ”ง Infra ยท ๐Ÿณ Container (Linux)' # This workflow builds the Quarto build container and pushes it to GitHub Container Registry # The container pre-installs all dependencies to eliminate 30-45 minute setup time # Includes comprehensive testing to ensure all components work properly # Prevent multiple builds running simultaneously concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true on: workflow_dispatch: inputs: force_rebuild: description: 'Force rebuild even if no changes' required: false default: false type: boolean no_cache: description: 'Disable Docker build cache (fresh build)' required: false default: false type: boolean container_registry: description: 'Container registry URL' required: false default: 'ghcr.io' type: string container_name: description: 'Container image name' required: false default: 'quarto-linux' type: string container_tag: description: 'Container tag' required: false default: 'latest' type: string workflow_call: inputs: force_rebuild: required: false default: false type: boolean no_cache: required: false default: false type: boolean container_registry: required: false default: 'ghcr.io' type: string container_name: required: false default: 'quarto-linux' type: string container_tag: required: false default: 'latest' type: string outputs: build-status: description: "Container build status (success/failure/skipped)" value: ${{ jobs.build.outputs.build-status }} image-name: description: "Full container image name with registry" value: ${{ jobs.build.outputs.image-name }} image-digest: description: "Container image digest (SHA256)" value: ${{ jobs.build.outputs.image-digest }} cache-hit: description: "Whether build used cache (true/false)" value: ${{ jobs.build.outputs.cache-hit }} # Re-enable automatic triggers schedule: - cron: '0 0 * * 0' # Weekly rebuild (Sunday at midnight) push: branches: [dev] # Only trigger on dev branch, not main paths: - 'book/tools/dependencies/**' - 'book/docker/linux/**' - '.github/workflows/book-build-linux-container.yml' env: # ============================================================================= # PATH CONFIGURATION - Uses GitHub Repository Variables (Settings > Variables) # ============================================================================= # MLSysBook content lives under book/ to accommodate TinyTorch at root # Use ${{ vars.BOOK_ROOT }}, ${{ vars.BOOK_DOCKER }}, etc. in workflow steps # Variables: BOOK_ROOT, BOOK_DOCKER, BOOK_TOOLS, BOOK_QUARTO, BOOK_DEPS # Container Registry Configuration (configurable via inputs) REGISTRY: ${{ (github.event_name == 'workflow_dispatch' && inputs.container_registry) || 'ghcr.io' }} IMAGE_NAME: ${{ github.repository }}/${{ (github.event_name == 'workflow_dispatch' && inputs.container_name) || 'quarto-linux' }} CONTAINER_TAG: ${{ (github.event_name == 'workflow_dispatch' && inputs.container_tag) || 'latest' }} # Container Build Configuration PLATFORM: linux/amd64 # Using vars.BOOK_DOCKER (repository variable) - works in all contexts DOCKERFILE_PATH: ./${{ vars.BOOK_DOCKER }}/linux/Dockerfile CONTEXT_PATH: . jobs: build: runs-on: ubuntu-latest if: github.repository_owner == 'harvard-edge' timeout-minutes: 90 # 1.5 hour timeout for Linux builds permissions: contents: read packages: write outputs: build-status: ${{ steps.build-summary.outputs.build-status }} image-name: ${{ steps.build-summary.outputs.image-name }} image-digest: ${{ steps.build-summary.outputs.image-digest }} cache-hit: ${{ steps.build-summary.outputs.cache-hit }} steps: - name: ๐Ÿ” Check workflow environment run: | set -euo pipefail # Exit immediately on any error echo "๐Ÿ” Checking workflow environment..." echo "๐Ÿ“Š Repository: ${{ github.repository }}" echo "๐Ÿ“Š Actor: ${{ github.actor }}" echo "๐Ÿ“Š Event: ${{ github.event_name }}" echo "๐Ÿ“Š Ref: ${{ github.ref }}" echo "๐Ÿ“Š SHA: ${{ github.sha }}" echo "๐Ÿ“Š Workflow: ${{ github.workflow }}" echo "๐Ÿ“Š Run ID: ${{ github.run_id }}" echo "๐Ÿ“Š Run Number: ${{ github.run_number }}" # Check if we have the required permissions echo "๐Ÿ” Checking permissions..." if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "โœ… Manual workflow dispatch - should have full permissions" elif [ "${{ github.event_name }}" = "push" ]; then echo "โœ… Push event - should have full permissions" elif [ "${{ github.event_name }}" = "schedule" ]; then echo "โœ… Scheduled event - should have full permissions" else echo "โš ๏ธ Unknown event type: ${{ github.event_name }}" fi # Check if secrets are available echo "๐Ÿ” Checking secrets availability..." if [ -n "${{ secrets.GITHUB_TOKEN }}" ]; then echo "โœ… GITHUB_TOKEN is available" else echo "โŒ GITHUB_TOKEN is not available" exit 1 fi echo "โœ… Environment check completed" - name: ๐Ÿ“ฅ Checkout repository uses: actions/checkout@v6 - name: ๐Ÿงน Free up disk space run: | echo "๐Ÿงน Freeing up disk space for large container build..." echo "๐Ÿ“Š Disk space before cleanup:" df -h / # Remove unnecessary packages and files sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/local/lib/android sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo docker system prune -af echo "๐Ÿ“Š Disk space after cleanup:" df -h / echo "โœ… Disk cleanup complete" - name: ๐Ÿณ Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: driver: docker-container driver-opts: | network=host buildkitd-flags: | --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host buildkitd-config-inline: | [worker.oci] max-parallelism = 1 [registry."docker.io"] mirrors = ["mirror.gcr.io"] - name: ๐Ÿ” Verify Buildx Builder run: | echo "๐Ÿ” Checking buildx builder status..." docker buildx ls echo "๐Ÿ” Inspecting builder..." docker buildx inspect --bootstrap echo "โœ… Buildx builder ready" - name: ๐Ÿ” Log in to Container Registry id: login uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: ๐Ÿ” Check registry access run: | set -euo pipefail # Exit immediately on any error echo "๐Ÿ” Checking container registry access..." echo "๐Ÿ“Š Registry: ${{ env.REGISTRY }}" echo "๐Ÿ“Š Repository: ${{ github.repository }}" echo "๐Ÿ“Š Actor: ${{ github.actor }}" echo "๐Ÿ“Š Event: ${{ github.event_name }}" # Test if we can access the registry echo "๐Ÿ” Testing Docker daemon access..." if docker info >/dev/null 2>&1; then echo "โœ… Docker daemon is accessible" else echo "โŒ Docker daemon not accessible" exit 1 fi # Test registry login with detailed error checking echo "๐Ÿ” Testing container registry login..." if echo "${{ secrets.GITHUB_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin; then echo "โœ… Successfully logged into container registry" else echo "โŒ Failed to log into container registry" echo "๐Ÿ” This could be due to:" echo " - Missing GITHUB_TOKEN secret" echo " - Insufficient permissions" echo " - Registry access issues" echo "๐Ÿ” Checking GITHUB_TOKEN availability..." if [ -n "${{ secrets.GITHUB_TOKEN }}" ]; then echo "โœ… GITHUB_TOKEN is available" else echo "โŒ GITHUB_TOKEN is empty or not available" fi exit 1 fi # Verify we can actually push to the registry echo "๐Ÿ” Testing registry write permissions..." TEST_IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/test-push:latest" if docker pull hello-world:latest >/dev/null 2>&1; then if docker tag hello-world:latest $TEST_IMAGE >/dev/null 2>&1; then if docker push $TEST_IMAGE >/dev/null 2>&1; then echo "โœ… Registry write permissions confirmed" # Clean up test image docker rmi $TEST_IMAGE >/dev/null 2>&1 || true else echo "โŒ Registry write permissions failed" exit 1 fi else echo "โŒ Failed to tag test image" exit 1 fi else echo "โŒ Failed to pull hello-world image for testing" exit 1 fi - name: ๐Ÿท๏ธ Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=raw,value=${{ env.CONTAINER_TAG }} - name: ๐Ÿณ Build Linux container id: build uses: docker/build-push-action@v6 with: context: ${{ env.CONTEXT_PATH }} file: ${{ env.DOCKERFILE_PATH }} load: true # Keep local copy for testing push: true # Also push to registry tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} no-cache: ${{ github.event_name == 'workflow_dispatch' && inputs.no_cache || false }} # Use cache unless explicitly disabled platforms: ${{ env.PLATFORM }} provenance: false # Disable provenance for better compatibility sbom: false # Disable SBOM for better compatibility cache-from: ${{ (github.event_name != 'workflow_dispatch' || !inputs.no_cache) && 'type=gha' || '' }} cache-to: ${{ (github.event_name != 'workflow_dispatch' || !inputs.no_cache) && 'type=gha,mode=max' || '' }} outputs: type=docker - name: Build Complete run: | echo "โœ… Linux container build completed successfully!" echo "๐Ÿ“Š Container: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.CONTAINER_TAG }}" - name: ๐Ÿ“Š Build Summary id: build-summary if: always() run: | # Determine build status if [ "${{ steps.build.outcome }}" = "success" ]; then BUILD_STATUS="success" else BUILD_STATUS="failure" fi # Extract build information IMAGE_NAME="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.CONTAINER_TAG }}" IMAGE_DIGEST="${{ steps.build.outputs.digest }}" CACHE_HIT="${{ steps.build.outputs.cache-hit }}" echo "build-status=$BUILD_STATUS" >> $GITHUB_OUTPUT echo "image-name=$IMAGE_NAME" >> $GITHUB_OUTPUT echo "image-digest=$IMAGE_DIGEST" >> $GITHUB_OUTPUT echo "cache-hit=$CACHE_HIT" >> $GITHUB_OUTPUT echo "๐Ÿ“Š Build Status: $BUILD_STATUS" echo "๐Ÿณ Image: $IMAGE_NAME" echo "๐Ÿ” Digest: $IMAGE_DIGEST" echo "๐Ÿ’พ Cache Hit: $CACHE_HIT"