Skip to content
Talk to our solutions team

Build every service from trunk

A complete set of binaries built from the mainline of every repository in your fleet, laid out by version and architecture, with repositories you do not want built opted out by a column in the list rather than by editing the flow.

When you finish you will have an artefact tree that a release flow can consume without knowing anything about how it was produced.

Building thirty services by hand is not hard, it is just unrepeatable.

Without thisWith this
Build order and flags live in someone’s shell historyThey live in a file with a diff history
Every developer’s binary is built slightly differentlyOne matrix — version, architecture, flags — applied to all
A service that should not ship gets built and shippedA FALSE in the list, visible to everyone
Artefacts land wherever the person was standingA predictable tree, so release automation needs no arguments
”Which commit is this binary?”The tree is versioned and the flow records it

The flag matrix is the hidden value. ldflags, target architecture and version being set once means every binary in the tree was built the same way — which is what makes the tree trustworthy as a release input.

You needWhy
The fleet on diskSee Clone or refresh a repository fleet
A prepared build machineSee Prepare a build machine
The same CSV listReused, with its build column now doing work

Everything that makes a binary different from another binary goes here, and nowhere else.

name: build-trunk
workingdirectory: ~/workspace
continueonerror: true
vars:
version: "1.2.0"
osarch: linux-amd64
ldflags: "-s -w"
only: ""
tables:
repos:
type: csv
file: ./fleet.csv

only is an escape hatch: empty means everything, otherwise a name to build just one. It costs one variable and saves you writing a second flow.

Step 2 — lay out the artefact tree first

Section titled “Step 2 — lay out the artefact tree first”

Create the destination before anything writes to it, so a build never fails on a missing directory halfway through.

tasks:
- name: prepare-tree
shell: |
mkdir -p build/{{version}}/{{osarch}}

Versioning the tree means two builds can coexist, which is what makes a rollback a copy rather than a rebuild.

Step 3 — say out loud what you are skipping

Section titled “Step 3 — say out loud what you are skipping”

A silent skip and a silent failure look identical in a log.

- name: skip
table: repos
check: |
"{{build}}" === "FALSE"
shell: |
echo "not building {{name}} (build=FALSE)"
- name: build
table: repos
check: |
"{{build}}" === "TRUE" && ("{{only}}" === "" || "{{only}}" === "{{name}}")
shell: |
set -e
cd {{name}}
echo "building {{name}} @ {{version}}"
GOFLAGS=-trimpath go build -ldflags "{{ldflags}}" \
-o ../build/{{version}}/{{osarch}}/{{name}} ./...

The check is the whole opt-in mechanism: a row builds only if it says TRUE, and only if it matches only when only is set.

- name: manifest
shell: |
cd build/{{version}}/{{osarch}}
ls -1 > MANIFEST.txt
echo "built $(wc -l < MANIFEST.txt) artefacts for {{version}}/{{osarch}}"
sha256sum * > SHA256SUMS 2>/dev/null || true

The checksums are what let a release flow verify it is shipping what this flow produced.

name: build-trunk
list: true
workingdirectory: ~/workspace
continueonerror: true
vars:
version: "1.2.0"
osarch: linux-amd64
ldflags: "-s -w"
only: ""
tables:
repos:
type: csv
file: ./fleet.csv
tasks:
- name: prepare-tree
shell: |
mkdir -p build/{{version}}/{{osarch}}
- name: skip
table: repos
check: |
"{{build}}" === "FALSE"
shell: |
echo "not building {{name}} (build=FALSE)"
- name: build
table: repos
check: |
"{{build}}" === "TRUE" && ("{{only}}" === "" || "{{only}}" === "{{name}}")
shell: |
set -e
cd {{name}}
echo "building {{name}} @ {{version}}"
GOFLAGS=-trimpath go build -ldflags "{{ldflags}}" \
-o ../build/{{version}}/{{osarch}}/{{name}} ./...
- name: manifest
shell: |
cd build/{{version}}/{{osarch}}
ls -1 > MANIFEST.txt
echo "built $(wc -l < MANIFEST.txt) artefacts for {{version}}/{{osarch}}"
sha256sum * > SHA256SUMS 2>/dev/null || true

Reach for the flow when the fleet is more than a handful. It records which repositories completed, so a rerun after fixing one broken repository does not rebuild the other thirty.

Terminal window
kis flow -f build-trunk.yaml

Build one service while iterating:

Terminal window
kis flow -f build-trunk.yaml -v only=service-alpha

Cut a different version without editing:

Terminal window
kis flow -f build-trunk.yaml -v version=1.2.1

MANIFEST.txt should list what you expect. Compare its line count against the number of TRUE rows in the list — a mismatch means a build failed and continueonerror let the run finish, which is the behaviour you want but only if you check.

ChangeWhere
Cross-compilationAdd GOOS/GOARCH from osarch in the build task
Non-Go servicesA second table and a second build task; the tree layout is unchanged
Much fasterSee Build a fleet in parallel
Reproducible from tagsSee Build a tagged release (planned)