What Are Ansible Roles?
Ansible roles are a way to organize playbook content into reusable, self-contained units. Instead of writing one massive playbook, you break functionality into roles like nginx, postgresql, deploy-app — each with its own tasks, variables, templates, and files.
# Instead of a 500-line playbook, use roles:
- hosts: webservers
roles:
- common
- nginx
- deploy-app
- monitoring
Why Use Roles?
- Reusability — Write once, use across multiple playbooks and projects
- Organization — Predictable directory structure, easy to navigate
- Sharing — Publish to Ansible Galaxy or private Git repos
- Testing — Test each role independently with Molecule
- Collaboration — Teams work on different roles without conflicts
Role Directory Structure
roles/
└── nginx/
├── defaults/ # Default variables (lowest precedence)
│ └── main.yml
├── vars/ # Role variables (higher precedence)
│ └── main.yml
├── tasks/ # Task definitions
│ └── main.yml
├── handlers/ # Handlers (triggered by notify)
│ └── main.yml
├── templates/ # Jinja2 templates
│ └── nginx.conf.j2
├── files/ # Static files
│ └── index.html
├── meta/ # Role metadata and dependencies
│ └── main.yml
├── tests/ # Test playbooks
│ └── test.yml
└── README.md # Documentation
What Goes Where
| Directory | Purpose | Example |
|---|---|---|
defaults/ | Default values users can override | nginx_port: 80 |
vars/ | Internal role variables (harder to override) | _nginx_user: www-data |
tasks/ | The actual automation tasks | Install, configure, start |
handlers/ | Actions triggered by notify | Restart nginx |
templates/ | Jinja2 files with variables | nginx.conf.j2 |
files/ | Static files to copy as-is | SSL certs, scripts |
meta/ | Dependencies, author, license | Depends on common role |
Create a Role
Using ansible-galaxy init
ansible-galaxy init nginx
This creates the full directory structure automatically.
Manual Creation
Only create the directories you need — Ansible ignores missing ones:
mkdir -p roles/nginx/{tasks,templates,handlers,defaults}
Example: Complete nginx Role
defaults/main.yml
---
nginx_port: 80
nginx_server_name: "_"
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_root: /var/www/html
nginx_enabled: true
tasks/main.yml
---
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install nginx (RHEL)
ansible.builtin.dnf:
name: nginx
state: present
when: ansible_os_family == "RedHat"
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: "nginx -t -c %s"
notify: Restart nginx
- name: Ensure nginx is started and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: "{{ nginx_enabled }}"
handlers/main.yml
---
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};
events {
worker_connections {{ nginx_worker_connections }};
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen {{ nginx_port }};
server_name {{ nginx_server_name }};
root {{ nginx_root }};
location / {
try_files $uri $uri/ =404;
}
}
}
meta/main.yml
---
galaxy_info:
author: Your Name
description: Install and configure nginx
license: MIT
min_ansible_version: "2.14"
platforms:
- name: Ubuntu
versions: [jammy, noble]
- name: EL
versions: [8, 9]
dependencies:
- role: common
Use Roles in Playbooks
Basic Usage
---
- hosts: webservers
become: true
roles:
- nginx
With Variable Overrides
---
- hosts: webservers
become: true
roles:
- role: nginx
vars:
nginx_port: 8080
nginx_server_name: "example.com"
Multiple Roles
---
- hosts: webservers
become: true
roles:
- common # Base packages, users, SSH config
- nginx # Web server
- certbot # SSL certificates
- deploy-app # Application deployment
- monitoring # Prometheus node exporter
Conditional Roles
---
- hosts: all
become: true
roles:
- role: nginx
when: "'webservers' in group_names"
- role: postgresql
when: "'dbservers' in group_names"
include_role (Dynamic)
tasks:
- name: Include role based on OS
ansible.builtin.include_role:
name: "{{ ansible_os_family | lower }}_base"
Role Dependencies
In meta/main.yml, declare roles that must run first:
dependencies:
- role: common
- role: firewall
vars:
firewall_allowed_ports:
- "{{ nginx_port }}"
Dependencies run before the role's own tasks.
defaults/ vs vars/ — Variable Precedence
defaults/main.yml → Lowest precedence (easily overridden)
vars/main.yml → Higher precedence (harder to override)
Rule of thumb:
defaults/→ values users should customize (ports, paths, feature flags)vars/→ internal values users shouldn't change (package names, paths per OS)
# defaults/main.yml — user-facing
nginx_port: 80
nginx_ssl_enabled: false
# vars/main.yml — internal
_nginx_package_name: nginx
_nginx_config_dir: /etc/nginx
Install Roles from Ansible Galaxy
# Install a single role
ansible-galaxy install geerlingguy.nginx
# Install from requirements.yml
ansible-galaxy install -r requirements.yml
requirements.yml
---
roles:
- name: geerlingguy.nginx
version: "3.1.0"
- name: geerlingguy.postgresql
version: "3.4.0"
- src: https://github.com/myorg/my-role.git
scm: git
version: main
name: my-custom-role
Test Roles with Molecule
# Install Molecule
pip install molecule molecule-docker
# Initialize test scaffolding
cd roles/nginx
molecule init scenario --driver-name docker
# Run tests
molecule test
Related Articles
- Ansible Best Practices — Clean playbook conventions
- FQCN in Ansible — Fully Qualified Collection Names
- Ansible Tutorial for Beginners — Getting started guide
- Ansible Galaxy Roles — Download and manage roles
- Ansible Template Module — Generate configs with Jinja2
- Ansible Vault — Encrypt secrets in roles
Conclusion
Ansible roles are the standard way to organize automation into reusable, testable components. Create roles with ansible-galaxy init, use defaults/ for user-facing variables, and share via Galaxy or Git. Every production Ansible project should use roles.