The challenge
A fleet of servers running different operating systems drifts fast when it is managed by hand. Package managers differ, hardening steps get forgotten, and “how this box was set up” becomes tribal knowledge that lives in one person’s head.
Our approach
We put every host under version-controlled Ansible. Each playbook lives in its own directory beside its own configuration and inventory, so there is never ambiguity about what runs where. The control node connects over SSH as an unprivileged user and escalates with sudo only on the target — it never runs as root.
Because no single package module covers every OS, hosts are grouped by package manager with OS-specific plays. FreeBSD jails, which ship without Python, are handled with the Python-independent raw module. Every run is idempotent: re-running converges the fleet and changes nothing that is already correct.
What we deliver
- One control node managing 10+ hosts across Ubuntu, Fedora, and FreeBSD (including jails).
- Playbooks for patching, build-tool installation, and CI agent/controller setup — each idempotent.
- A read-only, fleet-wide update audit using async polling that never blocks on a slow host.
- Safety rails: --check dry-runs, --limit to scope subsets, and git as the change audit trail.
- Unreachable hosts surface in the play recap without stopping the run.
Example configuration
Hosts are grouped by package manager; the play escalates with become and stays idempotent. Preview any run with --check before it touches a thing.
[ubuntu]
abba
calibri
gabriel
[fedora]
taroo
[freebsd_jails]
jindi
jindi2- name: Patch Ubuntu hosts
hosts: ubuntu
become: true # sudo on the target; never run as root
tasks:
- name: Upgrade all packages
ansible.builtin.apt:
upgrade: dist
update_cache: true
register: result
- name: Reboot if the kernel changed
ansible.builtin.reboot:
when: result.changed and ansible_facts.pkg_mgr == 'apt'ansible-playbook update.yml --check # dry run — change nothing
ansible-playbook update.yml --limit calibri # scope to one host
ansible-playbook update.yml # converge the groupOutcome
Reproducible provisioning and auditable hardening with zero manual edits per run — preview changes before they land, and roll back through git if needed.