What Are Ansible Roles?
Ansible roles are a way of organising your automation code into reusable, self-contained units. Rather than writing monolithic playbooks, roles allow you to break down complex configurations into logical, manageable components.
A role encapsulates all the tasks, handlers, variables, templates, and files needed to configure a particular piece of software or service. This modular approach makes your automation code easier to maintain, test, and share.
“Roles let you automatically load related vars, files, tasks, handlers, and other Ansible artifacts based on a known file structure.”
— Ansible Documentation
Role Directory Structure
Ansible roles follow a standardised directory structure. Each subdirectory contains a main.yml file that Ansible automatically loads.
roles/
└── nginx/
├── tasks/
│ └── main.yml
├── handlers/
│ └── main.yml
├── templates/
│ └── nginx.conf.j2
├── files/
│ └── index.html
├── vars/
│ └── main.yml
├── defaults/
│ └── main.yml
├── meta/
│ └── main.yml
├── tests/
│ ├── inventory
│ └── test.yml
└── README.md| Directory | Purpose |
|---|---|
| tasks/ | Contains the main list of tasks to be executed by the role. |
| handlers/ | Contains handlers that may be triggered by tasks. |
| templates/ | Contains Jinja2 template files for dynamic configuration. |
| files/ | Contains static files to be copied to managed nodes. |
| vars/ | Contains variables with high precedence (not easily overridden). |
| defaults/ | Contains default variables with lowest precedence. |
| meta/ | Contains role metadata including dependencies. |
| library/ | Contains custom modules for use within the role. |
| tests/ | Contains test playbooks for the role. |
Creating Your First Role
The easiest way to create a new role is using the ansible-galaxy command, which generates the standard directory structure.
# Create a new role skeleton# This creates:# This creates:This creates:
nginx/
├── README.md
├── defaults/
│ └── main.yml
├── files/
├── handlers/
│ └── main.yml
├── meta/
│ └── main.yml
├── tasks/
│ └── main.yml
├── templates/
├── tests/
│ ├── inventory
│ └── test.yml
└── vars/
└── main.ymlBasic Role Tasks
# roles/nginx/tasks/main.yml
---
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Install Nginx (RedHat)
yum:
name: nginx
state: present
when: ansible_os_family == "RedHat"
- name: Copy Nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Restart Nginx
- name: Ensure Nginx is started and enabled
service:
name: nginx
state: started
enabled: yesUsing the Role in a Playbook
# site.yml# Or with role options# Or with role options# Or with role options# Or with role options# Or with role options# Or with role options# Or with role options# Or with role options# Or with role options# Or with role optionss
- name: Configure app servers
hosts: appservers
become: yes
roles:
- role: nginx
tags: ['nginx', 'webserver']
- role: docker
vars:
docker_edition: 'ce'
docker_users:
- deployDefaults vs Variables
Understanding the difference between defaults and vars is crucial for creating flexible, reusable roles.
defaults/main.yml
- • Lowest precedence
- • Easily overridden by users
- • Best for configurable options
- • Should have sensible defaults
vars/main.yml
- • Higher precedence
- • Harder to override
- • Best for internal constants
- • Use for computed values
# roles/nginx/defaults/main.yml# These can be overridden by users# roles/nginx/vars/main.yml# roles/nginx/vars/main.yml# roles/nginx/vars/main.yml# roles/nginx/vars/main.yml# roles/nginx/vars/main.yml# roles/nginx/vars/main.yml# roles/nginx/vars/main.yml# Debian default# roles/nginx/vars/main.yml# roles/nginx/vars/main.yml# Internal variables, OS-specificspecific
---
nginx_packages:
Debian:
- nginx
- nginx-extras
RedHat:
- nginx
nginx_service_name: nginx
nginx_config_dir: /etc/nginxUsing Handlers
Handlers in roles work the same as in playbooks. They're defined in handlers/main.yml and triggered by tasks using notify.
# roles/nginx/handlers/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.yml# In tasks/main.ymlmain.yml
- name: Update Nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- Validate Nginx config
- Reload NginxTemplates and Files
Templates use Jinja2 and allow dynamic configuration. Files are copied as-is without any processing.
# roles/nginx/templates/nginx.conf.j2
user {{ nginx_user }};
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections }};
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout {{ nginx_keepalive_timeout }};
server_tokens {{ nginx_server_tokens }};
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log {{ nginx_log_dir }}/access.log;
error_log {{ nginx_log_dir }}/error.log;
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.listen | default('80') }};
server_name {{ vhost.server_name }};
root {{ vhost.root }};
{% if vhost.locations is defined %}
{% for location in vhost.locations %}
location {{ location.path }} {
{{ location.config | indent(12) }}
}
{% endfor %}
{% endif %}
}
{% endfor %}
}Role Dependencies
Roles can depend on other roles. Dependencies are defined in meta/main.yml and are executed before the role itself.
# roles/wordpress/meta/main.yml
---
galaxy_info:
author: Ayodele Ajayi
description: WordPress installation role
license: MIT
min_ansible_version: "2.10"
platforms:
- name: Ubuntu
versions:
- jammy
- focal
- name: Debian
versions:
- bullseye
galaxy_tags:
- wordpress
- web
- php
dependencies:
- role: nginx
- role: php
vars:
php_version: "8.2"
php_extensions:
- mysql
- gd
- curl
- xml
- role: mysql
vars:
mysql_databases:
- name: wordpress
mysql_users:
- name: wordpress
password: "{{ wordpress_db_password }}"
priv: "wordpress.*:ALL"Ansible Galaxy
Ansible Galaxy is a hub for finding, sharing, and downloading community roles. It accelerates automation development by providing pre-built, tested roles.
| Command | Description |
|---|---|
| ansible-galaxy init role_name | Creates a new role skeleton |
| ansible-galaxy install geerlingguy.docker | Installs a role from Galaxy |
| ansible-galaxy list | Lists installed roles |
| ansible-galaxy remove role_name | Removes an installed role |
| ansible-galaxy search nginx | Searches for roles on Galaxy |
| ansible-galaxy info geerlingguy.nginx | Shows information about a role |
Requirements File
# requirements.yml# Install all requirements# Install all requirements# Install all requirements# Install all requirements# Install all requirements# Install all requirements# Install all requirements# Install all requirements# Install all requirementsuirements
ansible-galaxy install -r requirements.yml
ansible-galaxy collection install -r requirements.ymlTesting with Molecule
Molecule is the standard testing framework for Ansible roles. It automates the process of creating instances, running your role, and verifying the results.
# Install Molecule with Docker driver# Initialise Molecule in existing role# Initialise Molecule in existing role# This creates:# This creates:# This creates:# This creates:# This creates:s:
molecule/
└── default/
├── converge.yml
├── molecule.yml
└── verify.ymlMolecule Configuration
# molecule/default/molecule.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/converge.yml# molecule/default/verify.yml# molecule/default/verify.yml# molecule/default/verify.yml# molecule/default/verify.yml# molecule/default/verify.yml# molecule/default/verify.yml# molecule/default/verify.ymlerify.yml
---
- name: Verify
hosts: all
become: yes
tasks:
- name: Check Nginx is running
service:
name: nginx
state: started
check_mode: yes
register: nginx_service
failed_when: nginx_service.changed
- name: Test Nginx responds
uri:
url: http://localhost/
status_code: 200Running Molecule Tests
# Full test sequence# Individual steps# Login to instance for debugging# Login to instance for debugging# Login to instance for debugging# Login to instance for debugging# Login to instance for debugging# Login to instance for debugging# Run with specific scenario# Run with specific scenarioific scenario
molecule test -s alternative-scenarioBest Practices
Use Defaults for Flexibility
Place variables in defaults/main.yml so users can easily override them.
Keep Roles Focused
Each role should do one thing well. Compose multiple roles for complex setups.
Document Everything
Include a detailed README with requirements, variables, and examples.
Use Tags Wisely
Add tags to tasks for selective execution and debugging.
Test with Molecule
Automated testing ensures your roles work across different scenarios.
Handle OS Differences
Use conditionals or include files based on ansible_os_family.
Real-World Examples
Docker Role
# roles/docker/tasks/main.yml
---
- name: Install Docker dependencies
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
state: present
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg
state: present
- name: Add Docker repository
apt_repository:
repo: "deb https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable"
state: present
- name: Install Docker
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
- name: Add users to docker group
user:
name: "{{ item }}"
groups: docker
append: yes
loop: "{{ docker_users | default([]) }}"Users Role
# roles/users/defaults/main.yml# roles/users/tasks/main.yml# roles/users/tasks/main.yml# roles/users/tasks/main.ymlrs/tasks/main.yml
---
- name: Create user groups
group:
name: "{{ item.group | default(item.name) }}"
state: present
loop: "{{ users }}"
when: users_create_groups
- name: Create user accounts
user:
name: "{{ item.name }}"
group: "{{ item.group | default(item.name) }}"
groups: "{{ item.groups | default([]) }}"
shell: "{{ item.shell | default(users_default_shell) }}"
create_home: "{{ item.create_home | default(true) }}"
state: present
loop: "{{ users }}"
- name: Add SSH authorised keys
authorized_key:
user: "{{ item.0.name }}"
key: "{{ item.1 }}"
state: present
loop: "{{ users | subelements('ssh_keys', skip_missing=true) }}"Troubleshooting
Common issues and solutions when working with Ansible roles.
Role not found errors
Error: “the role 'my_role' was not found”
Common causes:
- Role not in expected path
- Typo in role name
- Galaxy role not installed
- roles_path not configured correctly
Solution:
# Check where Ansible looks for roles# Install missing Galaxy roles# Install missing Galaxy roles# Specify roles path in ansible.cfg# Specify roles path in ansible.cfg# List installed roles# List installed roles# List installed roles# List installed roles# List installed roleses
ansible-galaxy listRole dependency conflicts
Symptoms: Role runs multiple times or in wrong order
Common causes:
- Circular dependencies between roles
- Same role listed as dependency in multiple places
- Version conflicts in role dependencies
Solution:
# meta/main.yml - Pin dependency versions# Prevent duplicate execution# Prevent duplicate execution# Prevent duplicate execution# Prevent duplicate execution# Use include_role for conditional execution# Use include_role for conditional executionole for conditional execution
- name: Include role conditionally
include_role:
name: database
when: install_database | default(false)Molecule test failures
Symptoms: Tests pass locally but fail in CI, or container issues
Common causes:
- Docker not available in CI environment
- Systemd not running in container
- Network issues in container
- Platform-specific failures
Solution:
# molecule.yml - Use systemd-enabled image# Skip systemd tests when not available# Skip systemd tests when not available# Skip systemd tests when not available# Skip systemd tests when not available# Skip systemd tests when not available# Debug molecule issues# Debug molecule issues# Debug molecule issues# Debug molecule issues# Debug molecule issues# Debug molecule issueses
molecule --debug test
molecule login # Interactive debuggingHandler not running
Symptoms: Service not restarting after config changes
Common causes:
- Handler name doesn't match notify directive
- Handler in wrong file location
- Task failed before handler could run
- Handler already flushed
Solution:
# Ensure handler name matches exactly# tasks/main.yml# handlers/main.yml# handlers/main.yml# handlers/main.yml# handlers/main.yml# handlers/main.yml# handlers/main.yml# handlers/main.yml# handlers/main.yml# Force handler to run immediately# Force handler to run immediately# Force handler to run immediately# Listen for multiple notifications# Listen for multiple notificationsle notifications
- name: Restart app
service:
name: app
state: restarted
listen: "restart web services"Conclusion
Ansible roles are the cornerstone of maintainable, scalable automation. By organising your code into well-structured roles, you create reusable components that can be shared across projects and teams.
Key benefits of using roles include:
- Reusability: Write once, use across multiple projects and environments.
- Testability: Molecule enables automated testing across platforms.
- Shareability: Ansible Galaxy provides a platform for sharing and discovering roles.
- Maintainability: Clear structure makes roles easy to understand and update.
Start creating roles for your common automation tasks, test them with Molecule, and consider contributing to the Ansible community through Galaxy.
Frequently Asked Questions
An Ansible role is a way of organising your automation code into reusable, self-contained units. Rather than writing monolithic playbooks, roles allow you to break down complex configurations into logical, manageable components. A role encapsulates all the tasks, handlers, variables, templates, and files needed to configure a particular piece of software or service.
The key difference is variable precedence. Variables in defaults/main.yml have the lowest precedence and are easily overridden by users, making them ideal for configurable options with sensible defaults. Variables in vars/main.yml have higher precedence and are harder to override, making them suitable for internal constants and computed values that should not be changed by role consumers.
Ansible Galaxy is a hub for finding, sharing, and downloading community roles. It accelerates automation development by providing pre-built, tested roles that you can install using the ansible-galaxy command. You can also use Galaxy to share your own roles with the community or manage role dependencies using a requirements.yml file.
Molecule is the standard testing framework for Ansible roles. It automates the process of creating instances (using Docker, Vagrant, or cloud providers), running your role against those instances, and verifying the results. Molecule enables you to test your roles across multiple platforms and scenarios, ensuring reliability before deployment.
An Ansible role follows a standardised directory structure with subdirectories for tasks (main automation logic), handlers (triggered actions like service restarts), templates (Jinja2 files for dynamic configuration), files (static files to copy), vars (high-precedence variables), defaults (low-precedence, user-configurable variables), meta (role metadata and dependencies), and tests (test playbooks).
Role dependencies are defined in the meta/main.yml file. You can specify other roles that must run before your role, including Galaxy roles with specific versions. Dependencies are automatically executed in order before the main role tasks. You can also pass variables to dependencies to customise their behaviour for your specific use case.
References & Further Reading
- Ansible Roles Documentation- Official guide to creating and using roles
- Ansible Galaxy- Community hub for sharing roles and collections
- Molecule Documentation- Testing framework for Ansible roles
- Ansible Collections Guide- Packaging roles into collections
- Ansible Testing Strategies- Best practices for testing automation
- Red Hat Ansible Overview- Enterprise Ansible guidance

