Professional Services CI/CD & Release Automation
A Jenkins pipeline that builds & ships to a 10-node, multi-OS fleet
One push to main compiles a native binary on Ubuntu, Fedora, FreeBSD, Solaris,
and OpenBSD — in parallel — archives one artifact per platform, deploys the mainline build to
every host, and leaves a tagged GitHub release ready to publish. The whole fabric — the
controller, the agents, and their build toolchains — is stood up with idempotent Ansible
playbooks, not by hand.
What it is
A single Jenkins controller (gemini, an Ubuntu 24.04 VM)
orchestrates a fleet of ten build agents spanning five operating-system
families. The flagship job, ascii-monitor, is a multibranch
pipeline: Jenkins indexes the GitHub repository, and every branch or pull request
runs the Jenkinsfile committed at that exact revision — so a build is always the
newest code on its branch, and pull-request builds are isolated from the mainline.
Because the controller lives on a private LAN that GitHub cannot reach, the trigger is a periodic repository scan (every five minutes) rather than an inbound webhook. Indexing is cheap — it only starts a build when a head commit has actually moved — so the interval is simply the worst-case push-to-build latency, not wasted work.
The pipeline at a glance
ascii-monitor multibranch pipeline: trigger → index → parallel matrix build → archive → mainline deploy → release.How a change becomes a release
- Push to
main. No manual step — the controller picks it up on its next scan. - Periodic scan (every 5 min). Jenkins re-indexes the repo; a moved head commit queues a build (shown as
BranchIndexingCause). - Checkout the pinned
Jenkinsfile. The pipeline definition always comes from the commit being built. - Matrix build. The job fans out over all ten nodes (
agent { label "${NODE}" }) and runs the identicalgmakeon each. - Archive. Each cell archives its own native binary (
ascii-monitor-${NODE_NAME}). - Deploy — mainline only. A
when { branch 'main' }stage installs each cell's binary into/usr/local/binon the same host, via an atomiccp+mv(no "text file busy", no cross-node copying). PR builds compile and archive but never overwrite a live binary. - Cut a release. A green
mainbuild already holds a full set of per-OS artifacts, so a release just publishes them:gh release createtags the exact built commit and attaches each binary.
Privilege model, verified before it shipped. Seven agents connect as a sudo-capable user; the two FreeBSD jails connect as root (one has no sudo at all). The deploy step escalates only when it isn't already root, so a single stage works unchanged across every OS in the fleet.
The Ansible playbooks behind the pipeline
The CI fabric itself is infrastructure as code. Three idempotent Ansible playbooks stand up and maintain everything the pipeline depends on — run them against one box or the whole fleet and the result is the same every time:
| Playbook | Role in the pipeline | What it installs |
|---|---|---|
install-jenkins-controller | Stands up the controller (gemini) | Java 21, the official Jenkins LTS apt repo, the jenkins package, service on :8080 |
install-jenkins-client | Prepares every build agent | A Java JDK to run the agent + git for SCM checkouts (OS-correct package per host) |
install-build-tools | Gives agents a uniform build toolchain | C/C++, CMake, make/gmake, and C# (.NET on Linux, Mono on FreeBSD) |
Each playbook groups hosts by package manager and runs the OS-appropriate module — the apt
module on Ubuntu, dnf on Fedora, and the raw module via pkg on
FreeBSD (the jails have no Python) — so a single command covers a deliberately heterogeneous
fleet. The full write-up of all six fleet playbooks, with topology diagrams, is on the
dedicated Ansible page.
Need a pipeline that ships safely, every time?
Repeatable builds, tested before they deploy, on whatever mix of platforms you run — let's talk.