What is the difference between Ansible roles and playbooks?
Quick Answer
Playbooks are YAML files listing tasks to run on hosts. Roles package those tasks along with handlers, variables, templates, and files into a reusable, organized folder structure.
Detailed Answer
Imagine you are writing a cookbook. A playbook is like a single recipe page that lists every step to make a dish. A role is like a chapter that bundles related recipes, ingredient lists, prep notes, and techniques into one organized section you can reference from any recipe. You would not paste the 'how to make pasta dough' instructions into every pasta recipe -- you would just point to the pasta-dough chapter.
A playbook is the main entry point for Ansible automation. It is a YAML file with one or more plays, where each play picks a set of hosts and lists tasks to run on them. Playbooks can include variables, conditionals, loops, and handlers all in one file. They work great for simple, one-off tasks or small setups. But as your infrastructure grows, playbooks get long, hard to maintain, and difficult to reuse across projects.
Roles fix this by enforcing a standard folder structure that separates concerns. A role has subdirectories for tasks (tasks/main.yml), handlers (handlers/main.yml), variables (vars/main.yml and defaults/main.yml), templates (templates/), files (files/), and metadata (meta/main.yml). When Ansible loads a role, it automatically pulls in files from each of these directories by convention. The defaults directory holds low-priority variables that users of the role can easily override, while vars holds higher-priority variables meant for the role's internal use. The meta directory declares dependencies on other roles, so one role can automatically pull in others it needs.
In production, teams organize their Ansible code as a collection of roles in a shared repository or distribute them through Ansible Galaxy. A top-level playbook then becomes a simple mapping of hosts to roles -- assign the nginx role, the postgresql role, and the monitoring-agent role to different host groups. This lets different teams own different roles independently. Roles can be versioned and pinned in a requirements.yml file, so you can lock specific versions for production while testing newer ones in staging.
A common mistake is misunderstanding how variable priority works between roles and playbooks. Variables in defaults/main.yml have the lowest priority and are easy to override, but variables in vars/main.yml have much higher priority and can unexpectedly overwrite playbook-level variables. Another mistake is making roles too small or too large -- the sweet spot is one role per logical service or component. Also, forgetting to list role dependencies in meta/main.yml can cause missing prerequisites when roles are reused in different playbooks.