You don’t really understand Ansible until you write your first playbook. Running ad-hoc commands is useful, but playbooks are where automation becomes repeatable, version-controlled, and actually scalable.
Let’s build one from scratch and talk through the decisions along the way.
What is an Ansible Playbook (in practical terms)?
An Ansible playbook is just a YAML file that defines what should happen on which machines. Think of it as a script, but declarative — you describe the desired state, not the exact steps.
At its simplest, a playbook answers three questions:
- Which hosts are we targeting?
- What tasks should run?
- With what configuration?
A minimal working playbook
Here’s the smallest useful playbook you can write:
1- name: Install nginx on web servers
2 hosts: web
3 become: true
4
5 tasks:
6 - name: Install nginx package
7 apt:
8 name: nginx
9 state: present
10Save this as install-nginx.yml and run:
1ansible-playbook -i inventory install-nginx.ymlThat’s it. You’ve just automated package installation across multiple machines.
Breaking it down (without overcomplicating it)
hosts
This tells Ansible where to run:
1hosts: web“web” maps to a group in your inventory file.
become
This enables privilege escalation (sudo):
1become: trueWithout it, tasks like installing packages will fail on most systems.
tasks
This is where the actual work happens. Each task uses a module:
1- name: Install nginx package
2 apt:
3 name: nginx
4 state: present
5A common mistake developers make is treating tasks like shell scripts. Avoid that when possible — prefer built-in modules like apt, yum, copy, and service.
Adding real-world usefulness
A playbook that only installs a package isn’t very helpful. Let’s extend it:
1- name: Configure web server
2 hosts: web
3 become: true
4
5 vars:
6 nginx_port: 8080
7
8 tasks:
9 - name: Install nginx
10 apt:
11 name: nginx
12 state: present
13 update_cache: true
14
15 - name: Copy custom config
16 copy:
17 src: nginx.conf
18 dest: /etc/nginx/nginx.conf
19
20 - name: Ensure nginx is running
21 service:
22 name: nginx
23 state: started
24 enabled: true
25Now we’re doing three important things:
- Installing the package
- Applying configuration
- Ensuring the service is running
This is the core pattern of most Ansible playbooks.
Inventory matters more than you think
Your playbook depends on a clean inventory. A simple one looks like:
1[web]
2192.168.1.10
3192.168.1.11
4You can also use hostnames, cloud inventories, or dynamic sources. As your infrastructure grows, keeping inventory organized becomes just as important as writing the playbook itself.
Variables: small feature, big impact
Hardcoding values is fine for demos, but not for real systems. Variables make playbooks reusable.
Example:
1vars:
2 app_port: 3000
3Used inside a task:
1port: {{ app_port }}Here’s where things get interesting — variables can come from multiple places:
- Inline in the playbook
- Inventory files
- Group vars / host vars
- Command line overrides
This flexibility is powerful, but can also get messy if you don’t stay consistent.
Handlers: avoid unnecessary restarts
If you restart services every time, your playbooks become disruptive. Handlers fix that.
1tasks:
2 - name: Update nginx config
3 copy:
4 src: nginx.conf
5 dest: /etc/nginx/nginx.conf
6 notify: Restart nginx
7
8handlers:
9 - name: Restart nginx
10 service:
11 name: nginx
12 state: restarted
13The handler only runs if the file actually changes. That’s efficient and safer.
Idempotency (the quiet superpower)
Ansible playbooks are designed to be idempotent — running them multiple times shouldn’t break anything or repeat work unnecessarily.
For example:
- Installing a package that already exists → no change
- Copying an identical file → no change
- Starting a running service → no change
If your playbook isn’t idempotent, it’s a sign something’s off.
Common pitfalls when creating playbooks
- Overusing shell commands: Prefer modules whenever possible
- Ignoring errors: Don’t silence failures without reason
- Hardcoding values: Use variables early
- Mixing concerns: Separate roles as complexity grows
When to move beyond a single playbook
Once your playbook grows past ~100 lines, consider splitting it into roles:
- web server role
- database role
- app deployment role
This keeps things maintainable and reusable across projects.
A quick mental model
An Ansible playbook is not a script. It’s a declaration of system state.
That mindset shift makes everything else easier — from debugging to scaling automation.
Wrapping up
Creating an Ansible playbook isn’t complicated, but writing a good one takes a bit of discipline. Start simple, rely on modules, keep things idempotent, and introduce structure as your use cases grow.
If your playbook can be run twice without surprises and still leaves the system exactly how you expect, you’re on the right track.