Created
November 8, 2025 15:12
-
-
Save Radiergummi/eec012d154ce0400032f3b1ad96451ac to your computer and use it in GitHub Desktop.
Conditional builds based on context hash
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 | |
| on: | |
| workflow_call: | |
| outputs: | |
| image-name: | |
| description: Name of the Docker image built | |
| value: ${{ jobs.finalize.outputs.image-name }} | |
| version: | |
| description: Version of the Docker image built | |
| value: ${{ jobs.finalize.outputs.version }} | |
| jobs: | |
| check-build-cache: | |
| name: Create Image Source Checksum | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: read | |
| outputs: | |
| hash: ${{ steps.docker-source-checksum.outputs.hash }} | |
| cache-hit: ${{ steps.set-image-ref.outputs.cache-hit }} | |
| cached-image: ${{ steps.set-image-ref.outputs.image }} | |
| cached-tag: ${{ steps.set-image-ref.outputs.tag }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| # Calculate a checksum of the Docker build context to use as a cache key. This action makes it possible to | |
| # determine if any of the build inputs have changed since the last build, and thus whether we can reuse a | |
| # previously built image or have to build a new one. | |
| - name: Create Image Source Checksum | |
| id: docker-source-checksum | |
| uses: matchory/docker-source-hash-action@v1.0.1 | |
| with: | |
| exclude-paths: | | |
| /.git/ | |
| # Now that we have the checksum, we can check the cache for an existing image reference for the same checksum. | |
| # The image reference (name and tag) is stored in a file that we cache using the checksum as part of the cache key. | |
| # This way, if the build inputs haven't changed, we can reuse the previously built image. | |
| - name: Restore Docker Image Reference from Cache | |
| uses: actions/cache/restore@v4 | |
| id: docker-cache | |
| with: | |
| key: docker-build-${{ steps.docker-source-checksum.outputs.hash }} | |
| path: .docker_image_ref | |
| # If we found a cached image reference, we need to verify that the image actually exists in the registry. | |
| # It's possible that the image was deleted from the registry but the cache entry still exists, so we need | |
| # to check for that. | |
| # If the image exists, we set the outputs accordingly so that the build job can skip the build step. | |
| - name: Set Image Reference | |
| id: set-image-ref | |
| shell: bash | |
| # language=bash | |
| run: | | |
| # Check for a cached image reference | |
| echo "::group::Checking for cached Docker image" | |
| # Read the image reference from the cached file, ignoring errors if the file doesn't exist | |
| image_ref=$(cat .docker_image_ref 2>/dev/null || true) | |
| image="" | |
| tag="" | |
| # Check if image with that tag exists in the registry | |
| if [ -n "${image_ref}" ] && [ "${{ inputs.skip-cache-check }}" != 'true' ]; then | |
| echo "Found cached image reference: ${image_ref}" | |
| manifest=$(docker manifest inspect "${image_ref}" 2>&1 || true) | |
| status=$? | |
| { | |
| echo "image-manifest<<EOF" | |
| echo "${manifest}" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| if [ $status -eq 0 ]; then | |
| echo "Cached image exists in registry." | |
| echo "::debug::Docker manifest inspect output: ${manifest}" | |
| # Split image_ref into image and tag on ':'. See here for how the character soup below works: | |
| # https://stackoverflow.com/a/15988793/2532203 | |
| image="${image_ref%%:*}" | |
| tag="${image_ref##*:}" | |
| else | |
| echo "::warning::A cached image reference was found, but the image does not exist in the registry. It may have been deleted and will be rebuilt." | |
| echo "Docker manifest inspect output: ${manifest}" | |
| fi | |
| else | |
| echo "::debug::No cached image reference found or cache check skipped." | |
| fi | |
| echo "::endgroup::" | |
| echo "image=${image}" >> $GITHUB_OUTPUT | |
| echo "tag=${tag}" >> $GITHUB_OUTPUT | |
| if [ -n "${image}" ] && [ -n "${tag}" ]; then | |
| echo "Using cached image: ${image}:${tag}" | |
| echo "cache-hit=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No cached image found." | |
| echo "cache-hit=false" >> $GITHUB_OUTPUT | |
| fi | |
| # Build the Docker image only if we didn't find a cached image in the previous step. | |
| build: | |
| if: needs.check-build-cache.outputs.cache-hit != 'true' | |
| name: Build Docker Image | |
| needs: check-build-cache | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| image-name: ghcr.io/${{ github.repository }} | |
| version: ${{ steps.meta.outputs.version }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to the Container registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ github.token }} | |
| - name: Set up Docker Metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/${{ github.repository }} | |
| - name: Build and Push | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| annotations: ${{ steps.meta.outputs.annotations }} | |
| cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache-${{ steps.meta.outputs.version }} | |
| cache-to: type=registry,mode=max,ref=ghcr.io/${{ github.repository }}:buildcache-${{ steps.meta.outputs.version }} | |
| provenance: mode=max | |
| sbom: true | |
| # Store the built image reference (name and tag) in a file we can store in the cache in the next step. | |
| - name: Store Docker Image Reference | |
| shell: bash | |
| # language=bash | |
| run: echo "ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}" > .docker_image_ref | |
| # Finally, save the image reference to cache so that it can be reused in future runs if the build inputs | |
| # haven't changed. | |
| - name: Save Docker Image Reference to Cache | |
| uses: actions/cache/save@v4 | |
| with: | |
| key: docker-build-${{ needs.check-build-cache.outputs.hash }} | |
| path: .docker_image_ref | |
| # Finalize job to output the image name and version, whether from cache or from the build step. By setting this step | |
| # to always run, we ensure that the outputs are always available to downstream jobs, regardless of whether the build | |
| # was skipped and the image info came from cache or was built anew. | |
| finalize: | |
| name: Finalize Build | |
| if: always() && !cancelled() | |
| needs: | |
| - check-build-cache | |
| - build | |
| runs-on: ubuntu-latest | |
| outputs: | |
| image-name: ${{ steps.final.outputs.image-name }} | |
| version: ${{ steps.final.outputs.version }} | |
| steps: | |
| - name: Set outputs | |
| id: final | |
| shell: bash | |
| # language=bash | |
| run: | | |
| echo "image-name=${{ needs.build.outputs.image-name || needs.check-build-cache.outputs.cached-image }}" >> $GITHUB_OUTPUT | |
| echo "version=${{ needs.build.outputs.version || needs.check-build-cache.outputs.cached-tag }}" >> $GITHUB_OUTPUT |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment