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

| Directory | Purpose | Priority | |-----------|---------|----------| | defaults/ | Default values users can override | Lowest | | vars/ | Internal role variables | High | | tasks/ | The automation tasks | — | | handlers/ | Event-triggered tasks | — | | files/ | Static files to copy to hosts | — | | templates/ | Jinja2 templates to render | — | | meta/ | Dependencies, platforms, license | — |

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

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 Galaxy: Install, Create & Share Roles and Collections (Guide)

Roles vs Playbooks

| Aspect | Playbook | Role | |--------|----------|------| | Scope | Complete automation | Single component | | Reusability | Low (project-specific) | High (across projects) | | Variables | Inline or vars files | defaults/ and vars/ | | File organization | Single file or flat | Standard directory structure | | Sharing | Copy/paste | Galaxy, Git, or tarball | | Testing | Complex | Molecule (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.

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: roles/ directory relative to the playbook Paths in roles_path setting in ansible.cfg ~/.ansible/roles (Galaxy default) /etc/ansible/roles (system default)

# ansible.cfg
[defaults]
roles_path = ./roles:~/.ansible/roles:/etc/ansible/roles

Best Practices

One role = one responsibility (don't mix nginx + MySQL in one role) Use defaults/ for user-configurable values, vars/ for internals Prefix variable names with the role name: webserver_port not port Document your role in README.md (Galaxy renders this) Test with Molecule before publishing Tag your tasks for selective execution Support multiple OS families using when: ansible_os_family 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.

Related Articles

publishing collections to Ansible Galaxyusing ansible.builtin.template effectivelylisten-based handlers in AnsibleAnsible conditional patternsinventory configuration in Ansible

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home