What Are Roles?

Roles are a way to organize playbooks into reusable components. Instead of one giant playbook, you split tasks, variables, templates, and files into a standard directory structure.

Directory Structure

roles/
  webserver/
    tasks/
      main.yml           # Main task list (required)
    handlers/
      main.yml           # Handler definitions
    templates/
      nginx.conf.j2      # Jinja2 templates
    files/
      index.html         # Static files
    vars/
      main.yml           # High-priority variables
    defaults/
      main.yml           # Default variables (lowest priority)
    meta/
      main.yml           # Role dependencies & metadata
    tests/
      test.yml           # Test playbook
    README.md            # Documentation

Create a Role

# Using ansible-galaxy
ansible-galaxy role init webserver

# Or create manually
mkdir -p roles/webserver/{tasks,handlers,templates,files,vars,defaults,meta}

tasks/main.yml

---
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present
  when: ansible_os_family == "Debian"

- name: Deploy configuration
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: restart nginx

- name: Start nginx
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

defaults/main.yml

---
nginx_port: 80
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_server_name: "_"
nginx_root: /var/www/html

handlers/main.yml

---
- name: restart nginx
  ansible.builtin.service:
    name: nginx
    state: restarted

- name: reload nginx
  ansible.builtin.service:
    name: nginx
    state: reloaded

templates/nginx.conf.j2

worker_processes {{ nginx_worker_processes }};

events {
    worker_connections {{ nginx_worker_connections }};
}

http {
    server {
        listen {{ nginx_port }};
        server_name {{ nginx_server_name }};
        root {{ nginx_root }};
    }
}

Using Roles

In a Playbook

- hosts: webservers
  roles:
    - webserver
    - { role: database, db_port: 5432 }
    - role: monitoring
      tags: monitoring

With import_role / include_role

- hosts: webservers
  tasks:
    - name: Apply webserver role
      ansible.builtin.import_role:
        name: webserver
      vars:
        nginx_port: 8080

    - name: Conditionally apply role
      ansible.builtin.include_role:
        name: ssl
      when: enable_ssl | bool

Variable Precedence

From lowest to highest priority:

  1. defaults/main.yml ← Role defaults (lowest)
  2. Inventory group_vars
  3. Inventory host_vars
  4. Playbook vars:
  5. vars/main.yml ← Role vars (high)
  6. Task vars:
  7. Extra vars (-e) ← Highest
# defaults/main.yml — users CAN override these
nginx_port: 80

# vars/main.yml — users should NOT override these
nginx_config_path: /etc/nginx

Role Dependencies

meta/main.yml

---
dependencies:
  - role: common
  - role: ssl
    vars:
      ssl_domain: "{{ server_domain }}"
  - role: firewall
    vars:
      firewall_allowed_ports:
        - "{{ nginx_port }}"

Galaxy Roles

# Install from Galaxy
ansible-galaxy role install geerlingguy.docker

# Install from requirements
ansible-galaxy install -r requirements.yml
# requirements.yml
roles:
  - name: geerlingguy.docker
    version: "6.1.0"
  - name: geerlingguy.nginx
  - src: https://github.com/example/custom-role.git
    name: custom-role
    version: main

Best Practices

  1. Use defaults for everything users might change
  2. Use vars for internal constants only
  3. Tag your roles for selective execution
  4. Include a README.md with usage examples
  5. Test with Molecule for CI/CD
  6. Keep roles focused — one role = one responsibility
  7. Use FQCN (fully qualified collection names) in tasks
# Good — FQCN
- ansible.builtin.apt:
    name: nginx

# Bad — short name (may conflict)
- apt:
    name: nginx

Conclusion

Roles are the building blocks of scalable Ansible automation. Use defaults/ for configurable variables, keep roles focused on a single responsibility, and share via Galaxy. Start every new project with roles from day one.