DEV Community

Cover image for Practical Guide to Trunk Based Development
Jon Lauridsen
Jon Lauridsen

Posted on • Updated on

Practical Guide to Trunk Based Development

(Image credit: midjourney)

Alright, last time we talked about the theory behind Trunk Based Development, of how scientific research shows its a good way of working, and some of the thoughts that should go into moving towards TBD.

Now we'll build the actual initial workflow to step into TBD, ending up with a set of simple scripts that can form the basis of TBD-workflows going forward. I truly love working like this.

Practical Trunk Based

Let's start with a core capability, and then build up to the final TBD-enabling scripts.

The Environment

First things is the crucial case of preventing environment drift. You and I and CI must all use the same versions of our tools and dependencies, and any drift must be identified and fixed quickly and easily.

There are many ways this can be done (e.g nix, pkgx, asdf, containers, etc.), and we won’t get into which specific tools to use because we’d be here all day. But we do need to ensure were synced.

Let’s create a script which, eh, I'm used to calling it doctor but you can pick your own name:

touch bin/doctor
chmod +x bin/doctor
Enter fullscreen mode Exit fullscreen mode

And we'll just do a first simple implementation :

set -euo pipefail
# This script guards against developer environment drift

[ "$(node --version)" = "v16.1.1" ] || { echo "Problem with Node.js"; exit 1; }
[ "$(npm --version)" = "$8.0.0" ] || { echo "Problem with npm"; exit 1; }

output=$(npm install --dry-run)
# Check if the output indicates packages would be changed
if echo "$output" | grep -q 'added\|removed\|updated'; then
    echo "Run npm install."
    exit 1
Enter fullscreen mode Exit fullscreen mode

This script checks that node and npm are installed and are of the correct version, and that dependencies are up-to-date. It's just an example, the script can be made as fancy as you like such as checking if nix is synchronised, or Docker images are compiled, or the database has all migrations applied, or whatever other things are important. The sky's the limit here.

As long as CI and production are also using the same underlying dependencies we can start relying on all our environments staying in sync, and anyone can run bin/doctor to verify their system is ready.


Next, let’s tackle a script to pull in changes:

$ cat bin/update
set -euo pipefail
# This script pulls in the latest changes, replacing `git pull`

git pull --rebase
npm ci
Enter fullscreen mode Exit fullscreen mode

This script forms the essence of our Trunk Based Development workflow: Instead of pulling code down by directly calling git pull this script now does that + all the other things we should remember to do after getting new changes. In this example-script bin/update pulls latest changes such that local changes will be rebased on top of the latest commit (no merge commits), and also installs new dependencies and runs bin/doctor to automatically catch environment drift (e.g. latest changes might have changed node version and that is now caught immediately).

So that's good. Of course, unlearning the habit of running git pull can be difficult for some, but it usually doesn't take more than a few days of reminding ourselves to use the script for the new habit to kick in.

Ship it

Now the core of our TBD-workflow, the shipit script:

$ cat bin/shipit
set -euo pipefail
# This script ships code, replacing `git push`

npm run test
npm run lint

if [[ -n $(git status --porcelain) ]]; then
  echo "There are changed files in the Git repository."
  exit 1

git push
Enter fullscreen mode Exit fullscreen mode

This is again just a simple example to illustrate the essential behavior of shipping, it first ensures we have the latest code, and that our environment is up-to-date, and that the code passes all tests and quality gates. And then the code gets pushed straight to main.

With a script like this, we've taken the guesswork out of pushing changes: Just make the change, and shipit. Trust that it'll catch errors.

The most difficult part can be to make it a habit: We have to practice running bin/shipit and trusting it will only ship good code. Don’t make branches. Just make the change and ship it. It takes a leap of faith to trust the workflow, but it’s worth pursuing to really minimise the time it takes to get code to production.

So, run bin/shipit after each commit, and start making many more much smaller commits. That causes code-changes to flow, and the application will continuously evolve and improve without any dusty branches that might explode into integration-nightmares when they're finally “done”.

Now we’re doing Trunk Based Development :)

What next?

There are so many variations to this pattern but for this article I think those three scripts show how we can safely start down the road of trunk based- and minimal touch-development:

Image showing three scripts: shipit, which calls update, which calls doctor

It's just a damned joy to commit && shipit and see 10-40-80 releases a day go out almost continuously. It’s instant feedback, and it's not only good science it's also very addictive to be able to react almost live to new and changing requirements.

Top comments (0)