Skip to content

Defaults vs Vars in Role


Back in the variable lookup order chapter, a simplified precedence model was built without roles in the picture at all. Roles add two more sources — defaults/main.yml and vars/main.yml — and they sit at genuinely opposite ends of that hierarchy, on purpose.

defaults/main.yml: The Lowest Precedence, On Purpose

roles/myapp/defaults/main.yml
app_port: 8080
app_name: "myapp"

Role defaults sit at the very bottom of the entire precedence hierarchy — even below inventory group variables. This is deliberate: a default is meant to be a sensible starting value, genuinely easy for anyone using the role to override, from almost anywhere.

vars/main.yml: Much Higher Precedence, On Purpose

roles/myapp/vars/main.yml
service_name: "myapp"

Role vars, by contrast, sit much higher — comparable to a play’s own vars:. These are meant to represent values the role’s own internal logic depends on staying consistent, not something a casual override from the outside should be able to change.

Proving It Concretely

group_vars/servers.yaml
app_port: 9090
service_name: "renamed-app"
roles/myapp/tasks/main.yml
- name: Show both values
  ansible.builtin.debug:
    msg: "port={{ app_port }} service={{ service_name }}"
ok: [ubuntu] => { "msg": "port=9090 service=myapp" }

app_port genuinely became 9090 — the group_vars override worked, since defaults/main.yml sits below it in precedence. service_name, though, is still myapp, not renamed-app — the exact same group_vars override, attempted the exact same way, silently had no effect at all, because vars/main.yml sits above group_vars in precedence. Same override attempt, same file, two completely different outcomes, purely because of which of the two role-variable locations each value happened to live in.

Stay tune till next chapter for practical. For now, just note the difference in your notebook and keep the very thing in your mind.

Why This Design Makes Sense

Defaults are the role’s suggested starting point — genuinely meant to be trivially overridden by whoever’s actually using the role, for their own environment. vars/main.yml values are things the role’s own tasks and templates depend on being consistent internally — if a template hardcodes an assumption tied to service_name, letting a casual inventory-level override silently break that internal consistency would be genuinely dangerous. The high precedence of vars/main.yml is a deliberate protection against exactly that.

A Gotcha: Putting The Wrong Kind Of Variable In The Wrong Place

If something that should be freely user-configurable — like app_port — ends up in vars/main.yml instead of defaults/main.yml, anyone using the role will find their attempts to override it mysteriously not taking effect, with no error at all telling them why. The reverse mistake is just as real: a value the role’s own logic genuinely depends on staying fixed, placed in defaults/main.yml instead, becomes far too easy to accidentally override in a way that breaks the role’s internal assumptions.

Best Practices

  • Put genuinely user-configurable settings in defaults/main.yml — anything you’d expect someone using your role to reasonably want to change for their own setup.
  • Put fixed internal values the role’s own logic depends on into vars/main.yml — things that shouldn’t casually change just because someone set a similarly-named inventory variable.
  • If a role’s user reports a variable that “isn’t taking effect” despite being set correctly, check whether that variable was mistakenly placed in vars/main.yml instead of defaults/main.yml first — this is almost always the actual cause.
Last updated on