All case studiesCI/CD & Release Automation

Safe, One-Command Deploys Across a Multi-OS Fleet

A Jenkins pipeline that turns one push to main into a correct native build on every machine — and a tagged release — in under five minutes.

JenkinsCI/CDMulti-OSAutomation

The challenge

Compiling and shipping software by hand across a fleet of machines running different operating systems is slow and error-prone. Each host needs its own native binary, builds must be reproducible, and a release should never depend on someone remembering the right sequence of commands.

Our approach

We use a controller-and-agent model: a single Jenkins controller orchestrates ten build agents spanning five OS families. A multibranch pipeline indexes the Git repository and runs the Jenkinsfile committed at that exact revision, so the build definition is always version-matched to the code.

The pipeline fans a build matrix across every node. A branch guard means pull requests compile and archive artifacts but never deploy, while a merge to main installs each freshly built binary. Installs are atomic — copy then move into place — which avoids the classic “text file busy” failure and guarantees a host is never left with a half-written binary.

git pushmainscan 5mJenkins controllermultibranchmatrix10 agents · 5 OSatomic installgh releaserelease

Technical specifics

  • One Jenkins LTS controller (Ubuntu, Java 21) driving 10 agents across Ubuntu, Fedora, FreeBSD, Solaris, and OpenBSD (two FreeBSD agents run as vnet jails).
  • A private-LAN fleet with no inbound webhooks, so builds trigger on a 5-minute repository scan.
  • Per-OS toolchains and package managers (apt / dnf / pkg) with C/C++, CMake, and C# via .NET and Mono.
  • Per-node artifact names and toolchain-grouped checksums that prove each host received its correct native build.
  • Releases cut with the GitHub CLI (gh release create) as a single step.

Example configuration

The Jenkinsfile fans the build across agents and only installs on main, with an atomic copy-then-move so a running binary is never overwritten in place.

Jenkinsfilegroovy
pipeline {
  agent none
  triggers { pollSCM('H/5 * * * *') }   // no webhooks on a private LAN
  stages {
    stage('Build matrix') {
      matrix {
        axes { axis { name 'NODE'; values 'ubuntu','fedora','freebsd','solaris','openbsd' } }
        stages {
          stage('compile') {
            agent { label "${NODE}" }
            steps {
              sh 'cmake -B build && cmake --build build'
              archiveArtifacts "build/ascii-monitor-${NODE}"
            }
          }
          stage('install') {
            when { branch 'main' }
            agent { label "${NODE}" }
            steps {
              // atomic install avoids "text file busy"
              sh 'cp build/ascii-monitor /usr/local/bin/.am.new && \
                  mv -f /usr/local/bin/.am.new /usr/local/bin/ascii-monitor'
            }
          }
        }
      }
    }
    stage('release') {
      when { branch 'main' }
      agent { label 'ubuntu' }
      steps { sh 'gh release create "v${BUILD_NUMBER}" build/ascii-monitor-*' }
    }
  }
}

Outcome

One push to main produces one native binary per operating system and a tagged GitHub release in under five minutes — with atomic installs preventing corruption and checksums proving correctness on every host.

Have a similar challenge?

We deliver this work end to end — from first commit to a secure, live deploy. Let’s talk about yours.