Skip to content

Using Role in a Play


A role sitting in roles/myapp/ does nothing on its own — it needs to actually be applied to a play. This chapter covers the common, simple way to do that, and gives import_role/include_role — previewed only lightly back in the includes and imports section — their full, working treatment.

Create myapp Role

Before anything else, create a myapp role:

ansible-galaxy role init roles/myapp

It will automatically creates roles folder and initialize an empty myapp role. myapp is nothing but a name of your role — name it anything that makes sense to your project.

The reason behind placing any role under roles is because Ansible automatically searches roles directory relative to the playbook file.

The roles: Keyword

playbook.yaml
- name: Deploy myapp
  hosts: servers
  roles:
    - myapp

This runs myapp’s tasks/main.yml against the servers group, automatically picking up its handlers, defaults, and vars along the way — no explicit include or import needed anywhere.

But if you run this playbook, nothing happens because there’s nothing inside tasks/main.yml yet. It will gather facts and complete:

ansible-playbook playbook.yaml
TASK [Gathering Facts] ********
ok: [ubuntu]

What roles: Actually Does Under The Hood

roles: is essentially shorthand for importing every listed role, at the play level, before anything else. Worth stating explicitly, since it affects execution order the moment a play has both roles: and its own tasks::

playbook.yaml
- name: Deploy myapp
  hosts: servers
  roles:
    - myapp
  tasks:
    - name: Extra step after the role
      ansible.builtin.debug:
        msg: "This runs after every role listed above"

Every role in roles: runs completely, in order, before the play’s own tasks: section starts at all — afterall any role is also a list of some tasks.

For practical, a simple task at myapp/tasks/main.yml:

myapp/tasks/main.yaml
- name: Say Hi
  ansible.builtin.debug:
    msg: Saying Hi

Now if you run the above playbook:


PLAY [Deploy myapp] ************************************************************

TASK [Gathering Facts] *********************************************************
ok: [ubuntu]

TASK [myapp : Say Hi] **********************************************************
ok: [ubuntu] => {
    "msg": "Saying Hi"
}

TASK [Extra step after the role] ***********************************************
ok: [ubuntu] => {
    "msg": "This runs after every role listed above"
}

PLAY RECAP *********************************************************************
ubuntu                     : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

import_role: The Same Static Resolution As Before

Used inside a tasks: list directly, giving finer control over exactly where a role’s tasks run relative to other tasks:

tasks:
  - name: Do something first
    ansible.builtin.debug:
      msg: "Before the role"

  - name: Import myapp role here specifically
    ansible.builtin.import_role:
      name: myapp

  - name: Do something after
    ansible.builtin.debug:
      msg: "After the role"

Resolved at parse time, exactly like import_tasks — the role name has to be fixed and known in advance. You can see it yourself:


PLAY [Deploy myapp] ************************************************************

TASK [Gathering Facts] *********************************************************
ok: [ubuntu]

TASK [Do something first] ******************************************************
ok: [ubuntu] => {
    "msg": "Before the role"
}

TASK [myapp : Say Hi] **********************************************************
ok: [ubuntu] => {
    "msg": "Saying Hi"
}

TASK [Do something after] ******************************************************
ok: [ubuntu] => {
    "msg": "After the role"
} 

include_role: The Same Dynamic Resolution As Before

tasks:
  - name: Include a role determined at runtime
    ansible.builtin.include_role:
      name: "{{ app_name }}_role"

Resolved at runtime, exactly like include_tasks — the role name can depend on a variable only known once the play is actually executing.

When Would You Use import_role/include_role Instead Of roles:?

roles: is the right default for the common case: apply one or more roles to a play, running before anything else in it. Reach for import_role/include_role specifically when a role’s tasks need to be interleaved at a particular point relative to other tasks — not just “always first” — or when the role’s name itself needs to be determined at runtime, which only include_role can do at all.

A Realistic Example: Multiple Roles In One Play

roles: accepts a list, applied in order:

- name: Full Server Setup
  hosts: servers
  roles:
    - common
    - myapp
    - monitoring

common runs first, then myapp, then monitoring — each one’s entire task list completing before the next one begins.

Best Practices

  • Use roles: for the common case — applying one or more roles to a play, running before the play’s own tasks.
  • Use import_role/include_role when a role’s tasks need to run at a specific point relative to other tasks, not simply first.
  • Use include_role specifically when a role’s name needs to be determined at runtimeimport_role can’t do this, for exactly the same static-resolution reason import_tasks couldn’t.
Last updated on