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:
defaults/main.yml← Role defaults (lowest)- Inventory
group_vars - Inventory
host_vars - Playbook
vars: vars/main.yml← Role vars (high)- Task
vars: - 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
- Use defaults for everything users might change
- Use vars for internal constants only
- Tag your roles for selective execution
- Include a README.md with usage examples
- Test with Molecule for CI/CD
- Keep roles focused — one role = one responsibility
- 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.