Professional Services Configuration Management

Idempotent Ansible playbooks for a mixed Linux & FreeBSD fleet

A fleet of Ubuntu, Fedora, and FreeBSD hosts — including FreeBSD vnet jails — provisioned, patched, and hardened from version-controlled YAML instead of tribal knowledge. Hosts are grouped by package manager, each play runs the OS-correct module, and re-running a playbook changes nothing it shouldn't.

Back to Professional Services
6
Playbooks
3
OS families
10+
Managed hosts
0
Manual edits per run

The approach

Every playbook lives in its own directory alongside the ansible.cfg and inventory.ini that belong to it — so there's never any ambiguity about which configuration goes with which playbook. The control machine reaches each host over SSH as an unprivileged user and escalates with become/sudo on the target; you run playbooks as yourself, never as root.

The fleet is deliberately heterogeneous, so a single package module can't cover it. Hosts are grouped by package manager and each play targets the right tool — the apt module on Ubuntu, dnf on Fedora, and the Python-independent raw module driving pkg on FreeBSD (the jails ship without Python).

Fleet topology

Ansible control node managing a fleet grouped by package manager A single Ansible control node reaches the fleet over SSH and escalates with sudo on each target. Hosts are grouped by package manager: apt for the Ubuntu hosts abba, calibri, gabriel, and zariel; dnf for the Fedora host taroo; pkg for the FreeBSD host lulu and its jails jindi and jindi2; plus a dedicated controller group for gemini, which runs the Jenkins server. Ansible control node user: storm · SSH key become: sudo on target apt Ubuntu 24.04 abba calibri gabriel zariel dnf Fedora 44 taroo pkg FreeBSD 15 lulu jindi jindi2 jails = root, no sudo controller patched, not built gemini · Jenkins :8080
One control node, one inventory per playbook, hosts grouped by package manager — each play runs the OS-correct module.

The playbooks

PlaybookPurposeChanges the system?
update-abbaFully upgrade an Ubuntu host (cache, dist-upgrade, autoremove) and report whether a reboot is requiredYes (apt)
check-updatesReport pending updates across the whole fleet — refreshes metadata and lists what would upgradeNo (read-only)
update-luluUpgrade a FreeBSD host (pkgbase: base + kernel + ports) and its vnet jails, in the correct reboot orderYes (pkg)
install-build-toolsInstall C/C++, CMake, make/gmake, and C# toolchains fleet-wideYes (installs packages)
install-jenkins-clientInstall Jenkins agent prerequisites — a Java JDK + git — on every nodeYes (installs packages)
install-jenkins-controllerInstall the Jenkins server (Java 21, LTS apt repo, service on :8080) on the controllerYes (installs packages)

Server provisioning & hardening, in practice

The provisioning playbooks bring a fresh host to a known-good state: install the right packages and toolchains, set up the Jenkins agent prerequisites, and report cleanly on what changed. The read-only check-updates playbook is the hardening companion — it audits every host's pending security and package updates without touching a thing, grouping output by OS so a long metadata refresh stays visible (async + poll) instead of looking frozen. Unreachable hosts are reported in the play recap and never stop the run.

Why it's safe to run

Anatomy of an idempotent playbook run A playbook run reads its inventory and ansible.cfg, gathers facts on each host, escalates to root, runs the OS-grouped package tasks, and reports. Re-running converges to the same state, changing nothing — and a check mode previews changes without applying them. inventory + cfg hosts grouped by OS Gather facts detect OS / state become: root sudo on target apt · dnf · pkg OS-grouped plays Report / --check verify, dry-run preview Idempotent — re-run converges to the same state, changing nothing
Tasks declare desired state, not steps — so a second run is a safe no-op, and --check previews changes before they're applied.

Dry-run first. Every changing playbook supports --check (and --limit to scope to a subset), so a change can be previewed on one host before it touches the fleet — and version control means every adjustment is reviewable in git.

The Jenkins controller and agents these playbooks install power the CI/CD pipeline — see how a push to main becomes a multi-OS build and release.

Want your servers described in code, not in your head?

Reproducible provisioning, fleet-wide patching, and hardening you can review in git — let's talk.