Side reading
Ansible without the enterprise
The fraction of Ansible a home server needs, and the vocabulary its documentation assumes you already have.
Ansible's documentation is written for people managing hundreds of machines. You have one. This is the subset that matters, and what the words mean.
The vocabulary #
Inventory: which machines exist. A YAML file listing hosts and variables. Yours has one host.
Play: a set of tasks applied to a set of hosts.
Playbook: a file containing one or more plays.
Task: one thing that should be true. Calls a module.
Module: the code that knows how to make something true and how to check whether it already is. ansible.builtin.copy, community.general.ufw.
Handler: a task that runs only if another task reported a change. Reloading a service after its config file changed.
Role: a directory structure that packages tasks, files and variables together. Useful past a few hundred lines; skip it until then.
Collection: a distributable bundle of modules. ansible.builtin ships with Ansible; anything else you install.
Fact: something Ansible discovered about the machine. Gathered automatically at the start of a play.
Idempotent: running it twice does what running it once did. The property the whole tool exists to provide.
What you actually need #
One inventory, one playbook, a files/ directory. That is a complete home server setup and it will stay readable at a few hundred lines.
homeserver/
inventory.yml
site.yml
files/
10-hardening.conf
52-upgrades
Add roles when site.yml becomes hard to navigate, not before. Splitting three tasks into a role structure is ceremony.
The modules worth knowing #
Nine of them cover nearly everything a home server needs:
| Module | For |
|---|---|
ansible.builtin.apt |
packages |
ansible.builtin.copy |
a file whose content you have |
ansible.builtin.template |
a file with variables in it |
ansible.builtin.file |
directories, permissions, symlinks |
ansible.builtin.service |
starting, enabling, reloading |
ansible.builtin.systemd_service |
when you need timers or a daemon-reload |
ansible.builtin.user |
accounts and group membership |
ansible.builtin.lineinfile |
one line in a file you do not own |
community.general.ufw |
firewall rules |
Prefer copy or template over lineinfile when you own the file. Owning the whole file means you know what is in it; editing lines in place means the result depends on what was there before.
The flags worth knowing #
$ ansible-playbook -i inventory.yml site.yml --check --diff
$ ansible-playbook -i inventory.yml site.yml --limit homeserver
$ ansible-playbook -i inventory.yml site.yml --tags firewall
$ ansible-playbook -i inventory.yml site.yml -v
--check --diff is the important one. Run it before every real run until you trust the playbook, and after that whenever the change touches something that could lock you out.
Things that catch people #
YAML indentation. Two spaces, never tabs. Most "syntax error" messages are a mis-indented line several lines above where it points.
become is not automatic. Add become: true at the play level and tasks run through sudo.
Reported changes that are not changes. A task that says changed every run is not idempotent. Usually command or shell, which cannot know whether the work was needed. Add a creates: or a when:, or find a module that does the job properly.
command versus shell. command does not use a shell, so no pipes or redirection, which is why it is safer. shell gives you a shell. Reach for a real module before either.
Handlers only run at the end of a play, and only if notified. If you need something reloaded immediately, meta: flush_handlers forces it.
Templates use Jinja2, the same syntax as {{ variable }} elsewhere. A .j2 file with variables, rendered on the target.
Where variables live #
More places than you need. For one machine, two are enough:
- In the inventory, next to the host or under
vars:. Good for anything machine-specific. - In the playbook, under
vars:. Good for things the playbook itself needs.
Ignore the precedence rules until you have a conflict. There are twenty-two levels and you will not encounter most of them.
What not to bother with #
Ansible Vault, if you already use SOPS. Two secret systems is one too many.
Dynamic inventory. For one host, a static file is correct.
AWX or Tower. A web interface for running playbooks, aimed at teams.
Testing frameworks. Molecule tests roles across distributions. Your test is --check and one machine.
When Ansible is the wrong tool #
It configures a host. It is not good at deploying applications, which is what compose is for, and it is not good at anything requiring conversation with a running system.
The split this book uses: Ansible owns the host, compose owns the containers. Keep that line clear and both stay simple.