Back to Blog
Ansible
Infrastructure as Code
DevOps

Ansible in Depth: Playbooks, Roles, and Idempotent Automation

OpsQuiz TeamAugust 5, 20264 min read30 views

Ansible's biggest design decision is also its simplest: no agent runs permanently on the machines it manages. It connects over plain SSH, pushes over a small Python payload, runs it, and disconnects. That agentless model is a big part of why it became the default choice for configuration management and ad-hoc automation across so many teams.

Inventory: telling Ansible what machines exist

Before running anything, Ansible needs to know which hosts it's managing. The simplest form is a static inventory file:

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.example.com ansible_user=admin

Groups ([webservers], [dbservers]) let you target a subset of your infrastructure at once. For anything beyond a handful of static machines, most teams switch to a dynamic inventory, a script or plugin that queries a cloud provider's API (AWS, Azure, GCP) at runtime and builds the host list automatically, so newly launched instances are picked up without anyone having to edit a file by hand.

Playbooks: the actual automation

A playbook is a YAML file describing what state you want a set of hosts to end up in, expressed as a sequence of tasks, each using a module (a small, focused unit of Ansible functionality, one for installing packages, one for managing files, one for starting services, and so on).

- name: Configure web servers
  hosts: webservers
  become: true
  tasks:
    - name: Install nginx
      apt:
        name: nginx
        state: present

    - name: Copy nginx config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx

    - name: Ensure nginx is running
      service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

become: true tells Ansible to escalate privileges (sudo, typically) for these tasks, since installing packages and managing system services isn't something a regular user can do. The notify and handlers pairing is worth noting: the handler only runs if the task that notified it actually changed something, so nginx doesn't get needlessly restarted on every single run, only when its config actually changed.

Idempotency: the property that makes reruns safe

This is the single most important concept in Ansible, and in configuration management generally. Idempotent means running the same playbook twice produces the same end result as running it once, the second run simply confirms everything is already in the desired state and makes no changes.

The apt task above with state: present doesn't blindly run apt-get install nginx every time, it first checks whether nginx is already installed, and only takes action if it isn't. This is exactly why running Ansible against a server that already matches the desired config is safe, and why it's the standard way to describe infrastructure as code, you're describing a desired end state, not a fixed sequence of one-time commands.

Contrast this with a raw bash script that just runs apt-get install nginx unconditionally, running it repeatedly might not break anything, but the safety isn't structural, you'd need to write that safety logic yourself for every single action. Ansible's modules give you that safety by default.

Roles: reusable, shareable structure

Once a playbook grows past a handful of tasks, or you find yourself wanting to reuse the same configuration logic across multiple projects, roles are the standard way to organize it:

roles/
  nginx/
    tasks/main.yml
    handlers/main.yml
    templates/nginx.conf.j2
    defaults/main.yml     # default variable values
    vars/main.yml         # variables meant to be overridden less often

A playbook then just references the role by name:

- name: Configure web servers
  hosts: webservers
  roles:
    - nginx
    - monitoring-agent

This structure is what makes Ansible Galaxy (a public registry of community-shared roles) useful, a well-written role for "install and configure PostgreSQL" can be dropped into any project's roles/ directory and just works, rather than every team reinventing the same automation from scratch.

ansible-vault: keeping secrets out of plain YAML

Playbooks often need secrets, database passwords, API keys, that obviously shouldn't sit in plain text in a Git repository. ansible-vault encrypts specific files (or specific variables within a file) so they can be committed safely, and only decrypted at runtime with a vault password:

ansible-vault encrypt group_vars/production/secrets.yml
ansible-playbook site.yml --ask-vault-pass

This is the built-in answer to "how do I keep credentials in version control without leaking them," rather than reaching for an entirely separate secrets management tool for a small team's needs.

Ad-hoc commands: when you don't need a whole playbook

Not everything needs a playbook. For a one-off task, Ansible's ad-hoc mode runs a single module against your inventory directly:

ansible webservers -m shell -a "df -h"
ansible webservers -m service -a "name=nginx state=restarted" --become

This is genuinely useful for quick checks across a fleet ("is disk space fine everywhere?") without writing and saving a playbook for something you'll run exactly once.

Ansible's whole design bet is that agentless, idempotent, human-readable YAML beats a more powerful but more complex agent-based system for the vast majority of real infrastructure work, and for most teams, that bet has held up well. The Infrastructure as Code quiz on OpsQuiz covers Ansible alongside Terraform if you want to test how the two compare.

    Welcome to OpsQuiz!

    Real scenario-based DevOps questions, hands-on practice, and clear explanations for every answer.