Docker Build and Publish #14
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: Docker Build and Publish | |
| on: | |
| # Trigger on GitHub releases for production builds | |
| release: | |
| types: [published] | |
| # Manual workflow dispatch for development/testing | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Docker tag to use' | |
| required: false | |
| default: 'dev' | |
| skip_tests: | |
| description: 'Skip ctest smoke tests' | |
| required: false | |
| type: boolean | |
| default: false | |
| dockerfile: | |
| description: 'Dockerfile to use' | |
| required: false | |
| default: './Dockerfile' | |
| jobs: | |
| # Validate DockerHub credentials before running expensive builds | |
| check-docker-token: | |
| name: Validate DockerHub Credentials | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Test DockerHub authentication | |
| run: | | |
| echo "Testing DockerHub credentials..." | |
| if echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin; then | |
| echo "✓ DockerHub credentials are valid" | |
| else | |
| echo "✗ DockerHub authentication failed!" | |
| echo "" | |
| echo "ERROR: The DockerHub credentials could not authenticate." | |
| echo "" | |
| echo "This usually means:" | |
| echo " - The Personal Access Token (PAT) has expired" | |
| echo " - The token has been revoked" | |
| echo " - The username or token is incorrect" | |
| echo "" | |
| echo "To fix this issue:" | |
| echo " 1. Go to https://hub.docker.com/settings/security to create a new PAT" | |
| echo " 2. Update the GitHub repository secrets:" | |
| echo " - DOCKERHUB_USERNAME: Your DockerHub username" | |
| echo " - DOCKERHUB_TOKEN: Your new DockerHub PAT" | |
| echo " 3. Navigate to: Settings > Secrets and variables > Actions" | |
| echo "" | |
| exit 1 | |
| fi | |
| # Generate metadata once to be shared across all jobs | |
| prepare-metadata: | |
| name: Prepare Build Metadata | |
| runs-on: ubuntu-latest | |
| outputs: | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| json: ${{ steps.meta.outputs.json }} | |
| primary-tag: ${{ steps.primary.outputs.tag }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Extract metadata (tags, labels) | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: pnnl/gridpack | |
| tags: | | |
| type=ref,event=branch | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| type=raw,value=${{ inputs.tag || 'dev' }},enable=${{ github.event_name == 'workflow_dispatch' }} | |
| - name: Extract primary tag | |
| id: primary | |
| run: | | |
| PRIMARY_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n 1) | |
| echo "tag=${PRIMARY_TAG}" >> $GITHUB_OUTPUT | |
| echo "Primary tag: ${PRIMARY_TAG}" | |
| # Build for each architecture using native runners | |
| build-native: | |
| name: Build ${{ matrix.platform }} on native runner | |
| needs: | |
| - prepare-metadata | |
| - check-docker-token | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-latest | |
| arch: amd64 | |
| - platform: linux/arm64 | |
| runner: ubuntu-24.04-arm | |
| arch: arm64 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to DockerHub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Build and push by digest | |
| id: build | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ${{ inputs.dockerfile || './Dockerfile' }} | |
| platforms: ${{ matrix.platform }} | |
| push: true | |
| labels: ${{ needs.prepare-metadata.outputs.labels }} | |
| outputs: type=image,name=pnnl/gridpack,push-by-digest=true,name-canonical=true,push=true | |
| cache-from: type=gha,scope=build-${{ matrix.arch }} | |
| cache-to: type=gha,mode=max,scope=build-${{ matrix.arch }} | |
| build-args: | | |
| boost_version=1.81.0 | |
| ga_version=5.9.1 | |
| petsc_version=3.24.2 | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| echo "Digest for ${{ matrix.arch }}: ${digest}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-${{ matrix.arch }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| # Merge the architecture-specific images into a multi-arch manifest | |
| merge-manifest: | |
| name: Create Multi-Architecture Manifest | |
| runs-on: ubuntu-latest | |
| needs: | |
| - prepare-metadata | |
| - build-native | |
| - check-docker-token | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: digests-* | |
| path: /tmp/digests | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to DockerHub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Create manifest list and push | |
| working-directory: /tmp/digests | |
| run: | | |
| # Parse all tags from metadata | |
| TAGS="${{ needs.prepare-metadata.outputs.tags }}" | |
| # Build docker buildx imagetools create command with all tags | |
| CMD="docker buildx imagetools create" | |
| for tag in ${TAGS}; do | |
| CMD="${CMD} -t ${tag}" | |
| done | |
| # Add all digests | |
| for digest in $(ls -1); do | |
| CMD="${CMD} pnnl/gridpack@sha256:${digest}" | |
| done | |
| echo "Creating manifest with command:" | |
| echo "${CMD}" | |
| eval "${CMD}" | |
| - name: Inspect multi-arch manifest | |
| run: | | |
| echo "Inspecting multi-architecture manifest..." | |
| TAG="${{ needs.prepare-metadata.outputs.primary-tag }}" | |
| docker buildx imagetools inspect ${TAG} | |
| # Test both architectures in parallel | |
| test-native: | |
| name: Test ${{ matrix.platform }} image | |
| needs: | |
| - prepare-metadata | |
| - merge-manifest | |
| - check-docker-token | |
| runs-on: ${{ matrix.runner }} | |
| if: ${{ !inputs.skip_tests }} | |
| continue-on-error: true | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-latest | |
| arch: amd64 | |
| - platform: linux/arm64 | |
| runner: ubuntu-24.04-arm | |
| arch: arm64 | |
| steps: | |
| - name: Log in to DockerHub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Run ctest smoke tests on ${{ matrix.arch }} | |
| run: | | |
| echo "Running ctest smoke tests on ${{ matrix.arch }}..." | |
| echo "Note: Test failures will NOT cause the workflow to fail (continue-on-error: true)" | |
| echo "Typical pass rate: ~94%" | |
| echo "" | |
| TAG="${{ needs.prepare-metadata.outputs.primary-tag }}" | |
| docker pull ${TAG} | |
| docker run --rm ${TAG} bash -c "cd /app/src/build && ctest -j" |