Synopsis #
Ansible and Salt can manage FreeBSD packages, files, rc.conf variables, and services. Neither tool makes an unsafe package upgrade, invalid configuration, or fleet-wide restart safe. Reliability comes from explicit file ownership, FreeBSD-aware modules, preflight validation, small rollout batches, and a tested recovery path.
Begin with the system’s manually reviewed source of truth. Persistent system settings distinguishes loader, sysctl, rc.conf, and service configuration; those boundaries should remain visible in automation.
Choose the control model #
| Requirement | Ansible | Salt |
|---|---|---|
| Agentless SSH-oriented execution | Primary model | Salt SSH is available, but not the default minion model |
| Resident agent and event-driven state | Not the normal architecture | Salt minion and master model |
| Small fleet with occasional convergent runs | Strong fit | Possible, with more infrastructure |
| Frequent state enforcement and event integration | Possible through external scheduling | Stronger native fit |
| FreeBSD package provider | community.general.pkgng | pkg virtual provider backed by pkgng on modern FreeBSD |
| FreeBSD rc.conf integration | community.general.sysrc | sysrc execution module or explicitly managed files |
Tool selection should follow the desired operating model, not a generic module-count comparison. The controller, state repository, secrets store, and execution credentials are production infrastructure and need their own backup and recovery plan.
Bootstrap deliberately #
Most Ansible POSIX modules require a Python interpreter on the managed host. FreeBSD base does not provide a general-purpose Python runtime. A bootstrap path may use Ansible’s raw action for the minimal reviewed package installation, after which normal modules can manage the runtime. Interpreter discovery and package naming must be tested on every supported host group.
Salt normally installs a minion and its runtime on each managed host. Salt SSH changes that topology but also changes available behavior and performance. Choose one documented enrollment path, authenticate the control endpoint, and treat master or roster credentials as secrets.
Do not allow an enrollment script to perform a general package upgrade. Bootstrap should establish only the management prerequisites and then stop for an ordinary reviewed convergence.
Assign one owner to every resource #
Maintain a resource register for at least these classes:
| Resource | Safe ownership rule |
|---|---|
| Installed package | One repository policy and one automation declaration |
/etc/rc.conf variable | A FreeBSD-aware rc.conf operation or one dedicated managed fragment |
/etc file | Preserve base-system semantics, owner, mode, and validation command |
/usr/local/etc file | Coordinate with the package’s sample, upgrade notes, and rc.d service |
| Secret | Retrieve from the designated secret store; never commit plaintext |
| Service restart | Trigger only after a validated material change |
| Loader or network change | Use a maintenance window and explicit recovery path |
Avoid managing the same file through a whole-file template, a line editor, and a local administrator. Convergence is undefined when several owners rewrite one resource.
For rc.conf values, prefer community.general.sysrc in Ansible rather than treating /etc/rc.conf as an arbitrary text file. Salt exposes a FreeBSD sysrc module, but a deployment should verify its available functions against the installed Salt release before encoding state around it.
Keep package operations bounded #
Ansible’s community.general.pkgng and Salt’s pkgng provider use FreeBSD pkg. They inherit the client’s configured repositories and the consequences of changing them.
Package state should name required packages and a deliberate version policy. A fleet-wide equivalent of an unreviewed pkg upgrade can remove or replace dependencies on every host. Review package-branch policy with choose quarterly or latest
, and qualify private packages with operate a signed poudriere repository
.
Before a broad change, capture the proposed package transaction on a representative host. Keep application configuration and package upgrades separate when either change can require a migration.
Validate files before activation #
Use whole-file management for configuration that automation fully owns. Set owner, group, and mode explicitly. Ansible copy and template support a validation command that checks a temporary candidate before replacing the destination. Salt file.managed has its own validation and test facilities; use the interface documented for the installed Salt release.
A safe service change follows this sequence:
- render the candidate without restarting the service;
- run the service’s native syntax checker against that candidate;
- replace the file only after validation succeeds;
- reload rather than restart when the service documents that behavior;
- test a real health condition, not only process existence;
- stop the rollout on the first failed host.
A handler or requisite should run only when the managed resource changed. Unconditional service restarts turn harmless convergence into an availability event.
Stage every rollout #
Ansible check mode and diff mode, and Salt test mode, predict changes where modules support them. They are review aids, not proof: external commands, package transactions, and service behavior can differ during a real run.
Use a rollout ladder:
- syntax and policy checks in the state repository;
- an expendable host matching production;
- one canary with console or out-of-band recovery;
- a small serial batch;
- the remaining fleet only after health and observation time.
Keep network, firewall, SSH, storage, and boot-loader changes out of an unrestricted all-host run. The safe PF change procedure is an example of a change that needs a rollback timer and independent management path.
For ZFS-root systems, a boot environment can improve recovery from package and configuration changes, but it does not protect external datasets or correct a broken network path. See boot environment recovery .
Audit drift without erasing evidence #
Schedule read-only convergence or drift reports separately from automatic remediation when a resource is operationally sensitive. An unexpected manual change can be evidence of an incident or an emergency repair. Capture the diff and owner before overwriting it.
Record tool version, collection or extension versions, state revision, target set, start and completion time, changed resources, and health result for every production run. Pin controller dependencies so a routine run does not silently adopt new module semantics.