AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible Roles: Complete Guide to Creating & Using Roles (2026)

By Luca Berton · Published 2024-01-01 · Category: installation

Complete guide to Ansible roles. Create, structure, use, and share reusable roles with defaults, handlers, templates, and Galaxy integration.

Ansible Roles: Complete Guide to Creating & Using Roles (2026)

What Are Ansible Roles?

Ansible roles are a way to organize automation content into reusable, self-contained units. Instead of writing everything in one massive playbook, roles let you break automation into logical components.

See also: Ansible Playbook Scanning Tools — 5 Best Tools for Secure Automation in 2026

Why Use Roles?

  • Reusability — Write once, use across projects
  • Organization — Clean separation of concerns
  • Sharing — Publish to Ansible Galaxy
  • Testing — Test each role independently with Molecule
  • Maintainability — Smaller, focused files are easier to manage

Role Directory Structure

roles/
└── webserver/
    ├── defaults/
    │   └── main.yml       # Default variables (lowest priority)
    ├── vars/
    │   └── main.yml       # Role variables (high priority)
    ├── tasks/
    │   └── main.yml       # Main task list
    ├── handlers/
    │   └── main.yml       # Handlers (e.g., restart services)
    ├── files/
    │   └── nginx.conf     # Static files to copy
    ├── templates/
    │   └── vhost.conf.j2  # Jinja2 templates
    ├── meta/
    │   └── main.yml       # Role metadata & dependencies
    ├── tests/
    │   ├── inventory
    │   └── test.yml       # Test playbook
    └── README.md          # Documentation

What Goes Where

DirectoryPurposePriority
defaults/Default values users can overrideLowest
vars/Internal role variablesHigh
tasks/The automation tasks
handlers/Event-triggered tasks
files/Static files to copy to hosts
templates/Jinja2 templates to render
meta/Dependencies, platforms, license

Creating a Role

Using ansible-galaxy init

ansible-galaxy role init roles/webserver

Manual Example

roles/webserver/tasks/main.yml:

---
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present
    update_cache: true

- name: Deploy site config
  ansible.builtin.template:
    src: vhost.conf.j2
    dest: "/etc/nginx/sites-available/{{ domain }}"
  notify: Reload nginx

- name: Enable site
  ansible.builtin.file:
    src: "/etc/nginx/sites-available/{{ domain }}"
    dest: "/etc/nginx/sites-enabled/{{ domain }}"
    state: link
  notify: Reload nginx

- name: Ensure nginx is running
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

roles/webserver/handlers/main.yml:

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

roles/webserver/defaults/main.yml:

---
domain: example.com
listen_port: 80
document_root: "/var/www/{{ domain }}"

roles/webserver/templates/vhost.conf.j2:

server {
    listen {{ listen_port }};
    server_name {{ domain }};
    root {{ document_root }};

    location / {
        try_files $uri $uri/ =404;
    }
}

Using Roles in Playbooks

Basic Usage

---
- name: Configure web servers
  hosts: webservers
  become: true
  roles:
    - webserver

With Variables

---
- name: Configure web servers
  hosts: webservers
  become: true
  roles:
    - role: webserver
      vars:
        domain: mysite.com
        listen_port: 8080

Multiple Roles

---
- name: Full server setup
  hosts: all
  become: true
  roles:
    - common          # Base config (users, SSH, firewall)
    - security        # Hardening
    - role: webserver # Nginx
      vars:
        domain: app.example.com
    - monitoring      # Prometheus node exporter

Conditional Roles

roles:
  - role: mysql
    when: "'databases' in group_names"

See also: Ansible Development: Write Custom Modules, Plugins & Collections

Roles vs Playbooks

AspectPlaybookRole
ScopeComplete automationSingle component
ReusabilityLow (project-specific)High (across projects)
VariablesInline or vars filesdefaults/ and vars/
File organizationSingle file or flatStandard directory structure
SharingCopy/pasteGalaxy, Git, or tarball
TestingComplexMolecule (per role)
Rule of thumb: If you'll use it more than once or it represents a logical component (database, webserver, monitoring), make it a role.

Role Dependencies

Define dependencies in meta/main.yml:

# roles/webserver/meta/main.yml
---
dependencies:
  - role: common
  - role: firewall
    vars:
      allowed_ports:
        - 80
        - 443

Dependencies run before the role's tasks.

See also: Ansible Galaxy: Install, Create & Share Roles and Collections (Guide)

defaults/ vs vars/

This is a common source of confusion:

# defaults/main.yml — Users CAN override these
http_port: 80            # Sensible default
max_connections: 100      # Tune per environment

# vars/main.yml — Internal, should NOT be overridden
nginx_package: nginx      # Implementation detail
config_path: /etc/nginx   # OS-specific constant

Variable precedence: defaults/ < inventory vars < playbook vars < vars/ < extra vars (-e).

Role Search Path

Ansible looks for roles in:

  1. roles/ directory relative to the playbook
  2. Paths in roles_path setting in ansible.cfg
  3. ~/.ansible/roles (Galaxy default)
  4. /etc/ansible/roles (system default)
# ansible.cfg
[defaults]
roles_path = ./roles:~/.ansible/roles:/etc/ansible/roles

Best Practices

  1. One role = one responsibility (don't mix nginx + MySQL in one role)
  2. Use defaults/ for user-configurable values, vars/ for internals
  3. Prefix variable names with the role name: webserver_port not port
  4. Document your role in README.md (Galaxy renders this)
  5. Test with Molecule before publishing
  6. Tag your tasks for selective execution
  7. Support multiple OS families using when: ansible_os_family
  8. Pin dependency versions in meta/main.yml

FAQ

How do I install a role from Galaxy?

ansible-galaxy role install geerlingguy.docker

Where are roles stored?

Default: ~/.ansible/roles/. Set custom path with roles_path in ansible.cfg.

Can a role include other roles?

Yes, via meta/main.yml dependencies or include_role/import_role in tasks.

What is the difference between include_role and import_role?

import_role is static (parsed at load time, supports tags). include_role is dynamic (parsed at runtime, supports loops and conditionals).

Conclusion

Roles are the foundation of maintainable Ansible automation. Start using roles early in your projects — the upfront structure pays off immediately in reusability and clarity.

For more tutorials, visit AnsiblePilot.

Create a Role

# Using ansible-galaxy
ansible-galaxy role init webserver

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

Role Directory Structure

roles/webserver/
├── defaults/main.yml     # Default variables (easily overridden)
├── vars/main.yml         # Role variables (high priority)
├── tasks/main.yml        # Main task list
├── handlers/main.yml     # Event handlers
├── templates/            # Jinja2 templates
│   ├── nginx.conf.j2
│   └── vhost.conf.j2
├── files/                # Static files
│   └── index.html
├── meta/main.yml         # Role metadata & dependencies
├── tests/                # Test playbooks
│   ├── inventory
│   └── test.yml
└── README.md

Use Roles in Playbooks

# Simple
- hosts: webservers
  roles:
    - webserver
    - monitoring

# With variables
- hosts: webservers
  roles:
    - role: webserver
      vars:
        http_port: 8080
        enable_ssl: true

# Conditional
- hosts: all
  roles:
    - role: docker
      when: install_docker | default(false)

Role with Tasks, Handlers, Templates

# roles/webserver/defaults/main.yml
http_port: 80
server_name: localhost
document_root: /var/www/html

# roles/webserver/tasks/main.yml
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present
  become: true

- name: Deploy config
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: "nginx -t -c %s"
  notify: restart nginx
  become: true

- name: Deploy site
  ansible.builtin.copy:
    src: index.html
    dest: "{{ document_root }}/index.html"
  become: true

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

# roles/webserver/handlers/main.yml
- name: restart nginx
  ansible.builtin.service:
    name: nginx
    state: restarted
  become: true

Role Dependencies

# roles/webapp/meta/main.yml
dependencies:
  - role: common
  - role: webserver
    vars:
      http_port: 8080
  - role: firewall
    vars:
      open_ports: [80, 443, 8080]

Include Role Dynamically

# Inline (dynamic)
- include_role:
    name: webserver
  vars:
    http_port: 9090

# Static import
- import_role:
    name: webserver

# Specific tasks file
- include_role:
    name: webserver
    tasks_from: ssl-setup

Galaxy Roles

# Install
ansible-galaxy role install geerlingguy.docker
ansible-galaxy role install -r requirements.yml

# requirements.yml
roles:
  - name: geerlingguy.docker
    version: "7.0.0"
  - name: geerlingguy.nginx
  - name: custom_role
    src: git+https://github.com/org/role.git
    version: main

Role Testing with Molecule

cd roles/webserver
molecule init scenario
molecule test

Best Practices

  • One role = one responsibility (webserver, database, monitoring)
  • Use defaults/ for user-configurable variables
  • Use vars/ for internal constants
  • Document variables in README.md
  • Tag your tasks for selective execution
  • Test with Molecule before sharing
  • Version your roles in requirements.yml

FAQ

Role vs playbook — when to use which?

Roles for reusable components (install nginx, configure database). Playbooks for orchestration (deploy full application stack using multiple roles).

How do I override role defaults?

Set variables in play vars, group_vars, host_vars, or extra vars (-e). All have higher priority than role defaults.

Can roles call other roles?

Yes — via meta/main.yml dependencies (auto-run) or include_role/import_role in tasks (manual).

Create a Role

ansible-galaxy role init my_role

Role Structure

my_role/
├── defaults/main.yml    # Default variables (lowest priority)
├── vars/main.yml        # Role variables (higher priority)
├── tasks/main.yml       # Main task list
├── handlers/main.yml    # Handlers
├── templates/           # Jinja2 templates
├── files/               # Static files
├── meta/main.yml        # Dependencies, Galaxy metadata
└── README.md

Use a Role

# In playbook
- hosts: webservers
  roles:
    - nginx
    - { role: myapp, app_port: 8080 }
    - role: monitoring
      when: enable_monitoring

Role Defaults

# defaults/main.yml — users can override these
app_port: 8080
app_workers: 4
app_log_level: info
app_user: appuser

Role Tasks

# tasks/main.yml
- name: Install packages
  ansible.builtin.apt:
    name: "{{ app_packages }}"
    state: present
  become: true

- name: Deploy config
  ansible.builtin.template:
    src: config.conf.j2
    dest: "/etc/{{ app_name }}/config.conf"
  notify: restart app

- name: Ensure service running
  ansible.builtin.service:
    name: "{{ app_name }}"
    state: started
    enabled: true
  become: true

Role Dependencies

# meta/main.yml
dependencies:
  - role: common
  - role: geerlingguy.docker
    vars:
      docker_edition: ce

include_role vs roles

# Static (roles section) — runs before tasks
- hosts: all
  roles:
    - nginx

# Dynamic (include_role) — runs inline with tasks
- hosts: all
  tasks:
    - debug: msg="Before role"
    - include_role:
        name: nginx
      when: install_nginx
    - debug: msg="After role"

Role with Tags

- hosts: all
  roles:
    - role: webserver
      tags: [web, deploy]

Organize Multiple Roles

project/
├── site.yml
├── inventory/
├── group_vars/
└── roles/
    ├── common/
    ├── webserver/
    ├── database/
    └── monitoring/

FAQ

defaults vs vars?

defaults/ has lowest priority — users easily override. vars/ has higher priority — harder to override. Use defaults/ for most role variables.

How to pass variables to a role?

roles:
  - role: myapp
    vars:
      app_port: 9090

Can a role include other roles?

Yes — via meta/main.yml dependencies or include_role/import_role in tasks.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home