diff --git a/.github/workflows/call-local-docker-build-promotion.yaml b/.github/workflows/call-local-docker-build-promotion.yaml new file mode 100644 index 0000000..e817031 --- /dev/null +++ b/.github/workflows/call-local-docker-build-promotion.yaml @@ -0,0 +1,61 @@ +--- +# THIS IS NOT A TEMPLATE. +# This is just for testing the repo itself. +# This calls the reusable workflow from its local file path. +name: Docker Build with Promotion + +on: + push: + branches: + - main + paths-ignore: + - 'README.md' + - '.github/linters/**' + pull_request: + paths-ignore: + - 'README.md' + - '.github/linters/**' + +# cancel any previously-started, yet still active runs of this workflow on the same branch +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +permissions: + contents: read + packages: write + pull-requests: write + +jobs: + + # run this job on every push to a PR + # it will push images to GHCR, but not DockerHub + docker-build-pr: + name: Call Build on PR + if: github.event_name == 'pull_request' + uses: ./.github/workflows/reusable-docker-build.yaml + with: + dockerhub-enable: false + ghcr-enable: true + push: true + image-names: | + ghcr.io/${{ github.repository }} + + # run this job on every push to the default branch (including merges and tags) + # it will push images to GHCR and DockerHub + # tags will also include ones like `stable--` and `latest` + docker-build-merge: + name: Call Build on Push + # this if is filtered to only the main branch push event (see events at top) + if: github.event_name == 'push' + uses: ./.github/workflows/reusable-docker-build.yaml + secrets: + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + with: + dockerhub-enable: true + ghcr-enable: true + push: true + image-names: | + docker.io/${{ github.repository }} + ghcr.io/${{ github.repository }} diff --git a/.github/workflows/call-local-docker-build.yaml b/.github/workflows/call-local-docker-build.yaml index b8bb82b..1fb3542 100644 --- a/.github/workflows/call-local-docker-build.yaml +++ b/.github/workflows/call-local-docker-build.yaml @@ -1,7 +1,7 @@ --- # THIS IS NOT A TEMPLATE. -# This is just for linting the gha-workflows repo itself. -# We call the reusable workflow from its file path. +# This is just for testing the repo itself. +# This calls the reusable workflow from its local file path. name: Docker Build on: @@ -42,9 +42,8 @@ jobs: with: - dockerhub-enable: true + dockerhub-enable: false ghcr-enable: true image-names: | - ${{ github.repository }} ghcr.io/${{ github.repository }} diff --git a/.github/workflows/reusable-docker-build.yaml b/.github/workflows/reusable-docker-build.yaml index bcf6069..1707b76 100644 --- a/.github/workflows/reusable-docker-build.yaml +++ b/.github/workflows/reusable-docker-build.yaml @@ -6,23 +6,17 @@ 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) - # - Builds on PR with tag of `prNUMBER` (same tag each PR push) - # - Builds on push to main branch with tag of `latest` - # - Builds on tag push with semver + # - 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: - dockerhub-enable: - description: Log into Docker Hub - required: false - default: false - type: boolean - ghcr-enable: - description: Log into GHCR - required: false - default: true - type: boolean comment-enable: description: Create a PR comment with image tags and labels required: false @@ -30,48 +24,76 @@ on: 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 - default: . + 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 - target: - description: Build stage to target + 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 - image-names: - description: A list of the account/repo names for docker build + push: + description: Push image to registry(s) required: false - type: string - default: | - ${{ github.repository }} - ghcr.io/${{ github.repository }} + 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=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=raw,value=stable-{{date 'YYYYMMDDHHmmss'}},enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=pr - type=raw,value=gha-${{ github.run_id }} - 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 + 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 - default: | - latest=false - + + secrets: dockerhub-username: description: Docker Hub username @@ -103,13 +125,10 @@ jobs: runs-on: ubuntu-latest outputs: - # only outputs the unique gha- image tag that's unique to each build + # only outputs the unique gha- image tag that's unique to each GHA run ghcr-tag: ${{ steps.ghcr-tag.outputs.tag }} steps: - - - name: Checkout - uses: actions/checkout@v3.5.0 - # we need qemu and buildx so we can build multiple platforms later name: Set up QEMU @@ -147,6 +166,8 @@ jobs: - # 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 @@ -160,10 +181,12 @@ jobs: # 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: true + push: ${{ inputs.push }} tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} - # add attestations for provenance and sbom + # 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 - @@ -200,6 +223,5 @@ jobs: - name: Find the gha-run-based image tag we just pushed to ghcr.io id: ghcr-tag run: | - echo '::echo::on' # shellcheck disable=SC2086 echo "tag=gha-${{ github.run_id }}" >> $GITHUB_OUTPUT diff --git a/README.md b/README.md index b2016f0..ffb16a3 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ -# Docker Build and Push GitHub Actions Workflow +# Template Repo: Docker Build and Push GitHub Actions Workflow [![Lint Code Base](https://github.com/BretFisher/docker-build-workflow/actions/workflows/call-super-linter.yaml/badge.svg)](https://github.com/BretFisher/docker-build-workflow/actions/workflows/call-super-linter.yaml) [![Docker Build](https://github.com/BretFisher/docker-build-workflow/actions/workflows/call-local-docker-build.yaml/badge.svg)](https://github.com/BretFisher/docker-build-workflow/actions/workflows/call-local-docker-build.yaml) A Reusable Workflow of the Docker GitHub Actions steps. Enhanced with learnings from production use. +> ⚠️ **DO NOT call this workflow directly**, rather, use it as a template repo and fork it for your own reusable workflow. I might change this workflow at anytime, based on new GHA features or learnings, and your calling workflow might break. ⚠️ + ## Reasons to use this workflow 1. Easier to start with than hand-building all the Docker Actions into a single workflow. 2. Provides inline docs based on real-world usage of this workflow. 3. Gives you inputs so you can reuse this workflow across many repositories and only needing the full workflow stored in a central repository. +4. New in 2023: Adds [SBOM and Provenance](https://docs.docker.com/build/attestations/) metadata to your images. +5. New in 2023: [Example template](./templates/call-docker-build-promote.yaml) to use the reusable workflow twice, in a "image promotion" style of dual registries (one for devs and PRs, one for production after PR merges) ## Steps to adopt this workflow @@ -36,10 +40,10 @@ A Reusable Workflow of the Docker GitHub Actions steps. Enhanced with learnings ## More reading -- [Docker Build/Push Action advanced examples](https://github.com/docker/build-push-action/tree/master/docs/advanced) +- [Docker Build/Push Action docs and examples](https://docs.docker.com/build/ci/github-actions/) - [My full list of container examples and tools](https://github.com/bretfisher) -## 🎉🎉🎉 Join my container DevOps community 🎉🎉🎉 +## 🎉🎉🎉 Join my cloud native DevOps community 🎉🎉🎉 - [My Cloud Native DevOps Discord server](https://devops.fan) - [My weekly YouTube Live show](https://www.youtube.com/@BretFisher) diff --git a/templates/call-docker-build-promote.yaml b/templates/call-docker-build-promote.yaml new file mode 100644 index 0000000..261ccac --- /dev/null +++ b/templates/call-docker-build-promote.yaml @@ -0,0 +1,69 @@ +--- +# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build-promote.yaml +name: Docker Build + +on: + push: + branches: + - main + # don't rebuild image if someone only edited unrelated files + paths-ignore: + - 'README.md' + - '.github/linters/**' + pull_request: + # don't rebuild image if someone only edited unrelated files + paths-ignore: + - 'README.md' + - '.github/linters/**' + +# cancel any previously-started, yet still active runs of this workflow on the same branch +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +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: + + # run this job on every push to a PR + # it will push images to GHCR, but not DockerHub + docker-build-pr: + name: Call Build on PR + if: github.event_name == 'pull_request' + # FIXME: fork this repo and update this path to YOUR reusable workflow location + uses: bretfisher/docker-build-workflow/.github/workflows/reusable-docker-build.yaml@main + with: + # NOTE: there are lots of input options for this reusable workflow + # read the comments in the inputs area of the reusable workflow for more info + # https://github.com/BretFisher/docker-build-workflow/blob/main/.github/workflows/reusable-docker-build.yaml + dockerhub-enable: false + ghcr-enable: true + push: true + image-names: | + ghcr.io/${{ github.repository }} + + # run this job on every push to the default branch (including merges and tags) + # it will push images to GHCR and DockerHub + # tags will also include ones like `stable--` and `latest` + docker-build-merge: + name: Call Build on Push + # this if is filtered to only the main branch push event (see events at top) + if: github.event_name == 'push' + # FIXME: fork this repo and update this path to YOUR reusable workflow location + uses: bretfisher/docker-build-workflow/.github/workflows/reusable-docker-build.yaml@main + secrets: + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + with: + # NOTE: there are lots of input options for this reusable workflow + # read the comments in the inputs area of the reusable workflow for more info + # https://github.com/BretFisher/docker-build-workflow/blob/main/.github/workflows/reusable-docker-build.yaml + dockerhub-enable: true + ghcr-enable: true + push: true + image-names: | + docker.io/${{ github.repository }} + ghcr.io/${{ github.repository }} diff --git a/templates/call-docker-build.yaml b/templates/call-docker-build.yaml index e042ec3..55ead96 100644 --- a/templates/call-docker-build.yaml +++ b/templates/call-docker-build.yaml @@ -26,6 +26,7 @@ jobs: name: Call Docker Build + # FIXME: fork this repo and update this path to YOUR reusable workflow location uses: bretfisher/docker-build-workflow/.github/workflows/reusable-docker-build.yaml@main permissions: @@ -37,49 +38,34 @@ jobs: # Only needed if with:dockerhub-enable is true below # https://hub.docker.com/settings/security - dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} - dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + # dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + # dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} with: - ### REQUIRED - ### ENABLE ONE OR BOTH REGISTRIES - ### tell docker where to push. - ### NOTE if Docker Hub is set to true, you must set secrets above and also add account/repo/tags below - dockerhub-enable: true - ghcr-enable: true + # NOTE: there are lots of input options for this reusable workflow + # read the comments in the inputs area of the reusable workflow for more info + # https://github.com/BretFisher/docker-build-workflow/blob/main/.github/workflows/reusable-docker-build.yaml + + # Here are just a few of the common defaults + + # dockerhub-enable: false + # ghcr-enable: true - ### A list of the account/repo names for docker build. List should match what's enabled above - ### defaults to: # image-names: | - # ${{ github.repository }} # ghcr.io/${{ github.repository }} - ### set rules for tagging images, based on special action syntax: - ### https://github.com/docker/metadata-action#tags-input - ### defaults to: - #tag-rules: | - # type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - # type=raw,value=stable-{{date 'YYYYMMDDHHmmss'}},enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - # type=ref,event=pr - # type=ref,event=branch - # type=raw,value=gha-${{ github.run_id }} - - ### path to where docker should copy files into image - ### defaults to root of repository (.) - # context: . - - ### Dockerfile alternate name. Default is Dockerfile (relative to context path) - # file: Containerfile + # tag-rules: | + # 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 - ### build stage to target, defaults to empty, which builds to last stage in Dockerfile + # context: "{{defaultContext}}" + # target: - ### platforms to build for - ### defaults to linux/amd64 - ### other options: linux/amd64,linux/arm64,linux/arm/v7 - # platforms: linux/amd64,linux/arm64 + # platforms: linux/amd64 - ### Create a PR comment with image tags and labels - ### defaults to true - # comment-enable: false + # comment-enable: true