Practical Guide to Trunk Based Development
Tags: devops, programming, development, git
Published: 2024-01-04 20:21:41

_(Image credit: midjourney)_

Alright, [last time](https://dev.to/jonlauridsen/beginners-intro-to-trunk-based-development-3158) we talked about the theory behind Trunk Based Development, of how scientific research shows its a good way of working, and some of the principles and thoughts that should go into moving towards TBD.

Today lets build an actual, initial workflow to step into TBD, ending up with a set of simple workflow scripts that form the basis of a TBD-workflow.

## Practical Trunk Based
Let's start with a core capability, and then build up to the final TBD-enabling workflow.

### The Environment
First thing is the crucial case of ensuring local developments stay in sync. We all probably practice this already, but it's even more important with TBD because the commit-frequency tends to increase and so changes have to be integrated much more often.

There are many tools to do this (e.g [nix](https://nixos.org), [pkgx](https://pkgx.sh), [asdf](https://asdf-vm.com), [containers](https://www.docker.com), etc.), and this isn't the article to decide which is best. For this article we'll just write an example of specifying our local environment via code, and you can imagine the implementation done using your favorite solution.

We'll create a script that I'm used to calling `doctor` because it says if the environment is healthy:

```shell
touch bin/doctor
chmod +x bin/doctor
```

And we'll just do our **really simple** implementation as an example of ensuring changes get integrated:

```shell
#!/bin/sh
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
fi
```

This script checks that `node` and `npm` are installed and are of the correct version, and that dependencies are up-to-date. The simple logic is **just** an example, but the point is developers can now run `bin/doctor` and know their environment is good to go. This is also the script to check if a local database is running, if migrations are applied, and any other preconditions that are critical for a local environment to be considered healthy.

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

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

git pull --rebase
npm ci
bin/doctor
```

This script forms the first leg of our **Trunk Based Development** workflow: Instead of pulling code by directly calling `git pull` this script does that + all the other things one should remember to do after getting new changes: Right now it's just ensuring dependencies are up to date, but it could also apply migrations and any other tasks that should happen after new code arrives. It also ends with running `bin/doctor` just as an additional safeguard against any lingering environment issues (e.g. latest changes might have upgraded node version and that is now caught immediately).

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 this script for the new habit to kick in.

### Ship it
Now to the core of our TBD-workflow, the `shipit` script:

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

bin/update
npm run test
npm run lint

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

git push
```

This is again just a simple example to illustrate the essential behavior of trunk-based shipping, by first ensuring we have the latest code, then that our environment is up-to-date, then that our code passes all tests and quality gates, and then finally pushing commits straight to main.

With a script like this, we've taken the guesswork out of pushing changes: Make the change and _shipit_. Learn to trust that it'll catch errors locally.

And that trust can be the most difficult part: We have to practice running `bin/shipit` frequently and trusting it will only let us ship good code. Don’t make branches; just make the change and _shipit_. It takes a leap of faith to start trusting this workflow, but once we do it hugely minimizes the time it takes to get code to production.

So, practice running `bin/shipit` after each commit, and then start practice making many more much smaller commits. That causes code-changes to flow smoothly, with all developers continuously pulling and pushing changes. The application starts to continuously evolve and improve without any dusty branches that risks exploding into integration-nightmares when they're finally “done”.

**Now** we’re doing Trunk Based Development :)

## What next?
We've now covered the essence of Trunk Based Development, showing how we can ensure changes can be safely pulled and pushed continuously using some simple local workflow scripts:

![Image showing three scripts: shipit, which calls update, which calls doctor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/09oq4n6b3radw75dru82.jpg)

Just these three scripts allow us to safely start down the road of efficient, simple, and **fast** trunk based development. Don't be surprised when even small teams can generate 10-40-80 releases a day almost continuously.

And this isn't just good science, it's also very addictive 🤩: It's fun for programmers to so quickly land our changes, it's a great joy for product owners to see almost instant feedback, and it is an incredible power when reacting to customer feedback to be able to deliver changes in literally minutes.