Skip to content

Instantly share code, notes, and snippets.

@jakzal
Last active July 14, 2025 11:24
Show Gist options
  • Save jakzal/578fffea6834845bca209b848cb430f1 to your computer and use it in GitHub Desktop.
Save jakzal/578fffea6834845bca209b848cb430f1 to your computer and use it in GitHub Desktop.
Build, package, deploy a versioned service (example)
name: Build
on:
push:
branches: ["main"]
tags: ["*"]
pull_request:
jobs:
version:
name: Version
uses: ./.github/workflows/version.yml
test:
name: Test
run: make build test
package:
name: Build images
uses: ./.github/workflows/package.yml
needs: version
with:
image_version: ${{ needs.version.outputs.next_version }}
image_prefix: "acme/foo"
image_archive: "foo-images.tar"
build_cmd: "IMAGE_VERSION=${{ needs.version.outputs.next_version }} make build-production VERSION=${{ needs.version.outputs.next_version }}"
vulnerability-scan:
# since we do not fail the build for now, but the report is to get feedback, only run it on the main branch and tags
if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) && github.repository_owner == 'acme'
name: Vulnerability scan
uses: ./.github/workflows/vulnerability-scan.yml
needs: package
with:
image_archive: "foo-images.tar"
images_json: ${{ needs.package.outputs.images_json }}
publish:
if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) && github.repository_owner == 'acme'
runs-on: ubuntu-latest
name: Publish containers
needs: [package, test]
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
IMAGES: ${{ needs.package.outputs.images }}
steps:
- uses: actions/checkout@v4
- name: Download Docker Images
uses: actions/download-artifact@v4
with:
name: docker-images
- name: Load Docker Images
run: docker load -i foo-images.tar
- name: Set up AWS CLI
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-2
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Tag and Push Docker Images filtered
run: |
FILTERED_IMAGES=$(echo "$IMAGES" | tr ' ' '\n' | grep -v 'foo-fluentbit' | tr '\n' ' ')
echo "Filtered IMAGES: $FILTERED_IMAGES"
make aws-push-to-ecr AWS_ACCOUNT_ID="${{ secrets.AWS_ACCOUNT_ID}}" IMAGES="$FILTERED_IMAGES"
- name: Extract version from IMAGES and update SSM
run: |
TAG=$(echo "$IMAGES" | awk '{print $1}' | cut -d: -f2)
echo "Detected tag: $TAG"
aws ssm put-parameter --name "foo-service-version" --value "$TAG" --type String --overwrite
# Builds Docker images with the supplied build command (`build_cmd`),
# verifies images built properly with the supplied verification command (`verify_cmd`),
# tags them with the supplied image version (`image_version`),
# packages images that match the supplied prefix (`image_prefix`),
# and uploads them as a build artifact under the supplied archive name (`image_archive`).
# Packaged image names are exposed as `images` and `images_json`.
name: Package
on:
workflow_call:
inputs:
image_version:
description: The Docker image version
required: true
type: string
image_prefix:
description: The prefix of images to export"
required: true
type: string
image_archive:
description: The image archive name to upload
required: true
type: string
image_artifact:
description: The image artifact name to upload the archive with
required: false
type: string
default: docker-images
build_cmd:
description: The command to use to build Docker image(s)
required: true
type: string
verify_cmd:
description: The command to use to verify images built successfully
required: false
type: string
default: true
outputs:
images:
description: List of images that were built
value: ${{ jobs.package.outputs.images }}
images_json:
description: List of images that were built (JSON)
value: ${{ jobs.package.outputs.images_json }}
jobs:
package:
name: Build images
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
outputs:
images: ${{ steps.save.outputs.images }}
images_json: ${{ steps.save.outputs.images_json }}
steps:
- uses: actions/checkout@v4
- name: Build
run: ${{ inputs.build_cmd }}
- name: Verify
run: ${{ inputs.verify_cmd }}
- name: Save Docker Images
id: save
run: |
IMAGES=$(docker images | awk '$1 ~ /'${IMAGE_PREFIX//\//\\/}'/ && $2 ~ /^'$IMAGE_VERSION'$/ { print $1":"$2}' | tr '\n' ' ')
IMAGES_JSON=$(echo $IMAGES | tr " " "\n" | jq -R . | jq -c -s .)
echo "Images to be saved: $IMAGES"
echo "Images to be saved (json): $IMAGES_JSON"
docker save -o "$IMAGE_ARCHIVE" $IMAGES
echo "images=${IMAGES}" >> $GITHUB_OUTPUT
echo "images_json=${IMAGES_JSON}" >> $GITHUB_OUTPUT
env:
IMAGE_PREFIX: ${{ inputs.image_prefix }}
IMAGE_ARCHIVE: ${{ inputs.image_archive }}
IMAGE_VERSION: ${{ inputs.image_version }}
- name: Upload Docker Images as Artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.image_artifact }}
path: ${{ inputs.image_archive }}
# Determines the next version for the new release of the code base.
# The version is exposed as the `next_version`.
# The tag is used if the current commit is tagged (i.e. 1.2.3).
# Otherwise, the last known tag is used with the current commit suffix (i.e. 1.2.3-4a5ae30).
name: Version
on:
workflow_call:
outputs:
next_version:
description: "The next version to be released"
value: ${{ jobs.version.outputs.next_version }}
jobs:
version:
name: Determine version
runs-on: ubuntu-latest
outputs:
next_version: ${{ steps.version.outputs.next_version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine the next version
id: version
run: |
COMMIT_REF=$(if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then echo "${{ github.event.pull_request.head.sha }}"; else echo "${GITHUB_SHA:-HEAD}"; fi)
CURRENT_COMMIT=$(git rev-parse --short "$COMMIT_REF")
CURRENT_TAG=$([[ "${GITHUB_REF:0:10}" = "refs/tags/" ]] && echo ${GITHUB_REF#refs/tags/} || echo "")
LAST_TAG=$(git for-each-ref refs/tags --sort=-creatordate --format='%(refname:short)' --count=1)
NEXT_VERSION=$([[ "${CURRENT_TAG}" = "" ]] && echo "${LAST_TAG:-0.0.0}-${CURRENT_COMMIT}" || echo "$CURRENT_TAG")
echo "Next version: $NEXT_VERSION"
echo "next_version=${NEXT_VERSION}" >> $GITHUB_OUTPUT
name: Vulnerability scan
on:
workflow_call:
inputs:
images_json:
description: JSON List of images to scan
required: true
type: string
image_archive:
description: Image export archive name
required: true
type: string
image_artifact:
description: Artifact name that contains the image export
required: false
type: string
default: docker-images
jobs:
vulnerability-scan:
runs-on: ubuntu-latest
name: Scan for vulnerabilities
strategy:
matrix:
image: ${{ fromJSON(inputs.images_json) }}
steps:
- uses: actions/checkout@v4
- name: Download Docker Images
uses: actions/download-artifact@v4
with:
name: ${{ inputs.image_artifact }}
- name: Load Docker Images
run: docker load -i ${{ inputs.image_archive }}
- name: Determine the category
id: category
run: |
image=${{ matrix.image }}
category=${image%%:*}
echo "category=$category" >> $GITHUB_OUTPUT
# As below, using grype directly and reporting output to terminal, because we cannot use github advanced security
- name: Install Grype
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
- name: Scan ${{ matrix.image }} with Grype
run: |
image="${{ matrix.image }}"
safe_name="${image//[\/:]/_}"
grype "$image" -o table > "grype-${safe_name}.txt"
echo "SAFE_NAME=$safe_name" >> $GITHUB_ENV
- name: Upload Grype Report
uses: actions/upload-artifact@v4
with:
name: grype-report-${{ env.SAFE_NAME }}
path: grype-${{ env.SAFE_NAME }}.txt
# Commented out because we do not have github advanced security
# Either need to pay for a seat, or for this repo to become public at which point it is free
#
# - name: Scan ${{ matrix.image }}
# uses: anchore/scan-action@v5
# id: scan
# with:
# image: "${{ matrix.image }}"
# fail-build: false
# severity-cutoff: critical
# output-format: sarif
# - name: Upload Image Vulnerability Reports
# uses: github/codeql-action/upload-sarif@v3
# with:
# sarif_file: ${{ steps.scan.outputs.sarif }}
# category: ${{ steps.category.outputs.category }}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment