--- name: Docker Build and Push on: # REUSABLE WORKFLOW with INPUTS # to keep this workflow simple, assumptions are made: # - only able to push to Docker Hub and/or GHCR (GHCR by default) # - adds a comment to PRs of tags and label metadata # - you want to use GitHub cache for buildx image layers # - Builds on PR with tag of `pr-NUMBER` (same tag each PR push) # - Builds on push to default_branch will have a unique tag of `stable-YYYYMMDD-SHA` # - Builds on push to default_branch will have a reusable tag of `latest` (useful for easy human testing, not servers) # - Builds on a tag push with semver will also have a reusable tag of `latest` and also a semver tag # - Defaults to only linux/amd64 platform builds, but can build for others in parallel workflow_call: # allow reuse of this workflow in other repos inputs: comment-enable: description: Create a PR comment with image tags and labels required: false default: true type: boolean context: description: Docker context (path) to start build from # To set to a subdir, use format of "{{defaultContext}}:mysubdir" required: false type: string dockerhub-enable: description: Log into Docker Hub required: false default: false type: boolean file: description: Dockerfile to build, relative to context path required: false type: string flavor-rules: # https://github.com/marketplace/actions/docker-metadata-action#flavor-input description: Three rules to (optionally) set for tag-rules, latest, prefix, and suffix required: false type: string # will tag latest on a git tag push, or if you add a type=semver or type=match tag-rules # NOTE: if you are seeing `latest` retagged when you don't expect it, set this latest=false default: | latest=auto ghcr-enable: description: Log into GHCR required: false default: true type: boolean image-names: description: A list of the account/repo names for docker build to push to required: false type: string # this is cool because you can add multiple names to different registries # and docker-build-push step will push to all of them default: | ghcr.io/${{ github.repository }} platforms: description: Platforms to build for required: false type: string # common ones: linux/amd64,linux/arm64,linux/arm/v7 default: linux/amd64 push: description: Push image to registry(s) required: false type: boolean default: true tag-rules: # https://github.com/marketplace/actions/docker-metadata-action#tags-input description: Use docker-metadata action to create tags from a key-value pair list in CSV format required: false type: string # this ruleset will create one or more tags for each image in image-names # some fire in pr-only, some in push/merge-only # I still recommend reusable `latest` tag for human-friendly testing (not servers) # I like a full tag for prod images that reads something like `stable--` # Tags starting with `gha-` are unique to each PR commit, and used to test fresh images # rules with is_default_branch only create the tag if it's a push/merge to default branch # priority attribute is used to sort tags in the final list. The higher the value, # the higher the priority. The first tag in the list (higher priority) will be used as # the image version for generated OCI label and version output. default: | type=raw,value=stable-{{date 'YYYYMMDD'}}-{{sha}},enable={{is_default_branch}},priority=300 type=ref,event=tag,priority=200 type=raw,value=latest,enable={{is_default_branch}},priority=100 type=raw,value=gha-${{ github.run_id }},enable=${{github.event_name == 'pull_request'}},priority=200 type=ref,event=pr,priority=100 target: description: Build stage to target required: false type: string secrets: dockerhub-username: description: Docker Hub username required: false dockerhub-token: description: Docker Hub token required: false outputs: image-tag: description: "single-use image tag for GHA runs" value: ${{ jobs.build-image.outputs.image-tag }} # permissions: GITHUB_TOKEN are better set by the **calling** workflow # but we'll set defaults here for reference # https://docs.github.com/en/actions/using-workflows/reusing-workflows#supported-keywords-for-jobs-that-call-a-reusable-workflow permissions: contents: read packages: write # needed to push docker image to ghcr.io pull-requests: write # needed to create and update comments in PRs jobs: build-image: name: Build+Push runs-on: ubuntu-latest outputs: # only outputs the unique gha- image tag that's unique to each GHA run image-tag: ${{ steps.image-tag.outputs.image-tag }} steps: # we need qemu and buildx so we can build multiple platforms later - name: Set up QEMU id: qemu uses: docker/setup-qemu-action@v2.1.0 # BuildKit (used with `docker buildx`) is the best way to build images - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v2.5.0 - name: Login to DockerHub if: inputs.dockerhub-enable uses: docker/login-action@v2.1.0 with: username: ${{ secrets.dockerhub-username }} password: ${{ secrets.dockerhub-token }} - name: Login to GHCR if: inputs.ghcr-enable uses: docker/login-action@v2.1.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Docker meta id: docker_meta uses: docker/metadata-action@v4.4.0 with: # list of Docker images to use as base name for tags images: ${{ inputs.image-names }} flavor: ${{ inputs.flavor-rules }} tags: ${{ inputs.tag-rules }} # this will build the images, once per platform, # then push to one or more registries (based on image list above in docker_meta) # NOTE: this will not push if a PR is from a fork, where secrets are not available # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - name: Docker Build and Push id: build_image uses: docker/build-push-action@v4.0.0 with: platforms: ${{ inputs.platforms }} context: ${{ inputs.context }} file: ${{ inputs.file }} target: ${{ inputs.target }} builder: ${{ steps.buildx.outputs.name }} # it uses github cache API for faster builds: # https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api cache-from: type=gha cache-to: type=gha,mode=max push: ${{ inputs.push }} tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} # add attestations for provenance and sbom (bleeding edge BuildKit features) # NOTE: for now, this reults in `unknown/unknown` images in all registries but Hub # https://docs.docker.com/build/attestations/attestation-storage/ provenance: true sbom: true # If PR, put image tags in the PR comments # from https://github.com/marketplace/actions/create-or-update-comment - name: Find comment for image tags uses: peter-evans/find-comment@v2.3.0 if: github.event_name == 'pull_request' && inputs.comment-enable id: fc with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' body-includes: Docker image tag(s) pushed # If PR, put image tags in the PR comments - name: Create or update comment for image tags uses: peter-evans/create-or-update-comment@v3.0.0 if: github.event_name == 'pull_request' && inputs.comment-enable with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | Docker image tag(s) pushed: ```text ${{ steps.docker_meta.outputs.tags }} ``` Labels added to images: ```text ${{ steps.docker_meta.outputs.labels }} ``` edit-mode: replace # for dependent jobs, we need to output the unique tag for this GHA run # based on the docker_meta tag priority rules, the highest priority tag # will be sent to this output # this step output is sent to job output, which is sent to workflow output # use this tag in another job with needs..outputs.image-tag - name: Find the primary image tag we just pushed, and output it id: image-tag run: | # shellcheck disable=SC2086 echo "image-tag=${{ steps.docker_meta.outputs.version }}" >> $GITHUB_OUTPUT