Skip to content

Passing Variable Into Role


Roles are only genuinely reusable if you can feed them different values for different situations. This chapter covers three distinct ways to do that, and when each one is actually the right choice with examples.

Mechanism One: Just Set A Variable With Higher Precedence

Since defaults/main.yml sits at the very bottom of the precedence hierarchy, literally any other variable source naturally overrides it — no special role-specific syntax required at all:

roles/myapp/defaults/main.yml
app_port: 9090

defaults/main.yml is overridden by group_vars/servers.yaml:

group_vars/servers.yaml
app_port: 9090
playbook.yaml
- name: Deploy myapp
  hosts: servers
  roles:
    - myapp
roles/myapp/tasks/main.yml
- name: Display App Port
  ansible.builtin.debug:
    msg: "App Port = {{ app_port }}"

myapp’s tasks see app_port as 9090, sourced entirely from group_vars, with nothing on the roles: line itself needing to change at all.

TASK [myapp : Display App Port] ****************************
ok: [ubuntu] => {
    "msg": "App Port = 9090"
}
ok: [fedora] => {
    "msg": "App Port = 9090"
}

Mechanism Two: Parameters Directly On roles:

A roles: entry can be written as a dictionary instead of a bare string, with extra key/value pairs treated as variables passed specifically to that one role application:

roles:
  - role: myapp
    app_port: 80

Useful when you want a value set right there in the play itself, without needing a separate inventory or group_vars file for it. Anything we have set previously will be overridden:

TASK [myapp : Display App Port] ****************************
ok: [ubuntu] => {
    "msg": "App Port = 80"
}
ok: [fedora] => {
    "msg": "App Port = 80"
}

Mechanism Three: vars: Alongside include_role

When using include_role, a task-level vars: block passes variables specifically to that invocation:

- name: Include myapp with specific variables
  ansible.builtin.include_role:
    name: myapp
  vars:
    app_port: 2040
ok: [ubuntu] => {
    "msg": "App Port = 2040"
}
ok: [fedora] => {
    "msg": "App Port = 2040"
}

Comparing The Three

  • Mechanism one (an external variable source) fits genuinely environment-specific configuration — different values per environment — that belongs in inventory or group_vars regardless of which role happens to be reading it.
  • Mechanism two (roles: parameters) fits a quick, one-off override specific to how this particular play uses the role, kept visible right there in the play file.
  • Mechanism three (vars: with include_role) is functionally similar to mechanism two, for the dynamic, task-level form specifically — useful when you’re already using include_role for its runtime-name flexibility and want to pass variables at the same time.

A Realistic Example: The Same Role, Two Different Apps

This is the whole motivation from the start of this section, made concrete — one role, applied twice, with different data each time:

playbook.yaml
- name: Deploy Two Different Apps With The Same Role
  hosts: servers
  roles:
    - role: myapp
      app_name: "webapp"
      app_port: 8080
    - role: myapp
      app_name: "apiapp"
      app_port: 9090
roles/myapp/tasks/main.yml
- name: Display App Name and App Port
  ansible.builtin.debug:
    msg: "App Name = {{ app_name }} and App Port = {{ app_port }}"
ansible-playbook playbook.yaml
TASK [myapp : Display App Name and App Port] *******************************
ok: [ubuntu] => {
    "msg": "App Name = webapp and App Port = 8080"
}
ok: [fedora] => {
    "msg": "App Name = webapp and App Port = 8080"
}

TASK [myapp : Display App Name and App Port] *******************************
ok: [ubuntu] => {
    "msg": "App Name = apiapp and App Port = 9090"
}
ok: [fedora] => {
    "msg": "App Name = apiapp and App Port = 9090"
}

Since the parameters genuinely differ between these two entries, the role runs fully both times. The full rule governing exactly when Ansible does or doesn’t re-run an already-applied role is covered in the next chapter.

A Gotcha: Parameters On roles: Are Scoped To That Role Only

Setting app_port: 9090 as a parameter on one roles: entry does not leak into the play’s other tasks or other listed roles — it’s scoped specifically to that one role’s invocation, unlike an ordinary play-level vars:, which would be visible everywhere in the play. Worth being explicit about, since it’s a genuinely different scoping behavior than everything else in this course labeled simply vars:.

Best Practices

  • Use inventory/group_vars for genuinely environment-specific configuration that shouldn’t be hardcoded into any particular play at all.
  • Use roles: parameters (or vars: with include_role) for values specific to how one particular play applies a role — especially when applying the same role more than once with different data.
  • Remember roles: parameters are scoped to that specific role invocation, not the whole play.
Last updated on