Roles and project layout

Ansible · Practical · 12 min

Roles are how Ansible code stays organised and reusable beyond a single playbook.

A role is a directory with a fixed structure that Ansible loads automatically:

  • tasks/main.yml: the entry point,
  • handlers/main.yml, templates/, files/ (found without paths),
  • defaults/main.yml: default variables, the lowest precedence: the role's public, overridable interface,
  • vars/main.yml: internal variables with high precedence; not meant to be overridden,
  • meta/main.yml: metadata and dependencies; meta/argument_specs.yml validates role inputs.

ansible-galaxy role init roles/nginx scaffolds it.

Using roles:

  • roles: in a play runs them before the play's tasks.
  • ansible.builtin.import_role (static, resolved at parse time) or include_role (dynamic, at run time, can be looped or conditional) inside tasks.
  • Similarly import_tasks vs include_tasks for task files.

Design tips: one role per component (nginx, postgres, app), prefixed variable names (nginx_worker_processes) to avoid collisions, sensible defaults for everything, and no hard-coded hostnames. Keep site-specific values in inventory group_vars.

A typical project: ansible.cfg, inventory/ (with group_vars), site.yml importing per-tier playbooks (webservers.yml), roles/, and requirements.yml for external roles and collections.

Example

# roles/nginx/defaults/main.yml
nginx_worker_processes: auto
nginx_sites: []

# roles/nginx/tasks/main.yml
- name: Install nginx
  ansible.builtin.package: {name: nginx, state: present}
- name: Configure nginx
  ansible.builtin.template: {src: nginx.conf.j2, dest: /etc/nginx/nginx.conf}
  notify: Reload nginx

# roles/nginx/handlers/main.yml
- name: Reload nginx
  ansible.builtin.service: {name: nginx, state: reloaded}

# webservers.yml
- name: Web tier
  hosts: web
  become: true
  roles:
    - role: nginx
      vars: {nginx_worker_processes: 4}

Remember

  • Fixed layout: tasks, handlers, templates, files, defaults, vars, meta
  • defaults = overridable interface; vars = internal
  • import_* is static, include_* is dynamic

Watch out for

  • Putting tunable settings in vars/main.yml, where inventory variables can't override them.

Check yourself

  1. Where should a role put values that users are expected to override from group_vars?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html