Ansible earned its place in nearly every operations team for one reason: it automates servers using tools they already have. No agents to install, no certificates to distribute — just SSH (or WinRM for Windows) and Python on the target. You describe the state you want in YAML, and Ansible makes it so.
The moving parts
- Control node — the machine you run Ansible from (your laptop, a bastion, a CI runner).
- Inventory — the list of hosts you manage, organised into groups.
- Modules — the units of work:
apt,copy,service,user, and thousands more. - Playbooks — YAML files that map groups of hosts to ordered lists of tasks.
- Roles — a directory convention for packaging tasks, templates, and defaults into reusable components.
Inventory: name your world
# inventory/hosts.ini
[web]
web01.example.com
web02.example.com
[db]
db01.example.com
[production:children]
web
db
[production:vars]
ansible_user=deploy
Groups are the unit of targeting: playbooks run against web or
production, not hard-coded hostnames. In cloud environments, dynamic inventory
plugins can build this list live from your provider's API (OCI, AWS, Azure all have one), so
the inventory never goes stale.
Your first playbook
# site.yml
---
- name: Configure web servers
hosts: web
become: true
vars:
http_port: 80
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy site configuration
ansible.builtin.template:
src: templates/site.conf.j2
dest: /etc/nginx/conf.d/site.conf
notify: Reload nginx
- name: Ensure nginx is running and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Run it with ansible-playbook -i inventory/hosts.ini site.yml. Notice the
handler: nginx reloads only if the template task actually changed the file.
That's the heart of the Ansible model — react to change, don't repeat work.
Idempotency: the mindset shift
A shell script says "do these steps." A playbook says "this is the desired state." Run a good
playbook twice and the second run reports changed=0 — nothing needed doing.
This property, idempotency, is what makes automation safe to run repeatedly,
on a schedule, or against a half-configured server. Practical habits that preserve it:
- Prefer purpose-built modules (
apt,lineinfile,template) overshell/command. - When you must use
command, addcreates:orchanged_when:so Ansible knows whether anything changed. - Test with
--check(dry run) and--diffbefore touching production.
Variables, templates, and secrets
Variables layer up from role defaults → group_vars → host_vars → extra vars, letting one
playbook serve every environment. Templates use Jinja2
({{ http_port }}, loops, conditionals) to render config files per host. For
secrets, Ansible Vault encrypts variable files at rest —
ansible-vault encrypt group_vars/production/vault.yml — so credentials never sit
in plain text in your repository.
Where Ansible fits next to Terraform
A question every team asks. The clean division: Terraform provisions the infrastructure (VCNs, instances, load balancers) and Ansible configures what runs on it (packages, files, services, users). They meet in the middle — Terraform outputs feeding a dynamic inventory is a particularly tidy pattern.
Takeaway
Start small: one inventory, one playbook, one role. The pay-off arrives quickly — the third time you configure a server by running one command instead of an afternoon of SSH sessions, you'll wonder how you tolerated anything else. The next post takes this foundation to its most valuable production use case: patching entire fleets safely.
back to all posts