Github Actions for trunk based development
Overview
Goal: On Github make it easy to use trunk based development and releases to deploy an application to staging and production.
A set of supporting Github Actions outlined here are available to use.
Table of Contents
Trunk Based Development versus Git Flow on Github
Github default tooling makes it easy to automate working with branches using pull requests, but releases can be more difficult.
This can favor a Git Flow style of development. However, we can use the flexibility of Github Actions to make trunk based development work just as well.
In trunk based development work is performed on a long-lived trunk branch (usually "main"). Releases candidates are tagged from main or from short lived release branches made from main.
trunkbaseddevelopment.com
Git Flow is actually quite similiar, but the development branch is usually called "develop". Git Flow also specifies one additional step: merging to a long-lived release branch (usually "main") before the release.
Atlassian
In theory Git Flow creates the additional overhead of maintaining an additional long-lived branches with no benefit compared to trunk based development. In practice with Github the benefit is that the process can be managed entirely with branches and thus Github Pull Requests.
Overview of trunk based development on Github
There are 2 independent flows: development and release. Development is going to be a standard Pull Request workflow. Lets focus on the release flow:
- Create a release in Github
- This pushes deployment artifacts (e.g. push a container to a registry)
- auto-deploy the release to a staging environment
- Promote the release to being the "latest" release
- This will trigger a deployment to production
- The ability to quickly rollback is available- there is a manual deploy action that can select a (previous) release.
Many minor variations of the above are possible to suit your needs as long as they can be triggered either by release creation/promotion or manual workflow triggers.
Github Actions using release triggers
As a quick overview of Github actions:
- Github Actions can be re-used as a library with the
uses
clause. - They can also be invoked directly with workflow_call.
- An environment can be specified: this will apply different configuration values to the workflow
At the top level there is a prerelease action that calls a build or build-deploy action.
name: "deploy prerelease"
on:
release:
types:
push:
branches:
- 'test-ci/deploy-prerelease/*'
jobs:
deploy:
if: "${{\
startsWith(github.ref, 'refs/tags/release/prod') \
}}"
uses: ./.github/workflows/build-deploy.yaml
with:
# A prerelease is never deployed to production
# A production prerelease is auto-deployed to staging for preview
environment: "${{\
(startsWith(github.ref, 'refs/tags/release/prod') && 'staging') \
}}"
secrets: inherit
# If the production environment is isolated from the staging environment,
# then we need to build and push the production artifacts without deploying them.
# We can do this optimistically once the staging deployment is successful.
build-prod:
needs: deploy
if: "${{ startsWith(github.ref, 'refs/tags/release/prod') }}"
uses: ./.github/workflows/build.yaml
with:
environment: production
artifacts: true
notify: true
secrets: inherit
Marking a release as the latest release deploys to production:
name: Deploy release
on:
release:
types:
push:
branches:
- 'test-ci/deploy-release/*'
# This only calls deploy, assuming that deploy-prerelease already pushed the production artifacts
# If you are not using that flow you can use the build-deploy workflow instead
jobs:
build:
uses: ./.github/workflows/deploy.yaml
with:
environment: 'production'
secrets: inherit
Building and deploying
The build-deploy action combines building an deploying. You may want to make it callable. Similarly, the deploy action can be callable: this allows for a quick rollback. The build action can be re-used for pull requests.
name: Build and Deploy
on:
workflow_dispatch:
inputs:
environment:
description: environment to push docker image to
required: true
type: choice
options:
- qa
- staging
- production
workflow_call:
inputs:
environment:
description: environment to push docker image to
required: true
type: string
ref:
required: false
type: string
push:
branches:
- 'test-ci/build-deploy/*'
jobs:
# The build job validates and pushes artifacts
build:
uses: ./.github/workflows/build.yaml
with:
artifacts: true
ref: "${{inputs.ref}}"
environment: "${{inputs.environment}}"
secrets: inherit
deploy:
needs: build
uses: ./.github/workflows/deploy.yaml
with:
ref: "${{inputs.ref}}"
environment: "${{inputs.environment}}"
secrets: inherit
Automating release pushing
We can automate pushing a tag and creating a release. This script is designed for applications and assumes a date-based release versioning. It will:
- generate the tag with an incremented postfix if there are multiple releases in a day
- push the tag
- generate a url to click on to create a release in Github for the tag
#!/usr/bin/env bash
ENVIRONMENT=
# Find the next available tag
i=1
tag="release/ / /"
until ; do
i=
tag="release/ / /"
done
if repo= ; then
fi
Github will auto-generate release notes. There is still a manual button click to make the release and the selection of the previous release to compare against.
Conclusion
Github already has a release feature that is perfect for trunk based development. If we add in some additional automation trunk based development can be just as easy to use on Github as Git flow.