Prefixing Variables and Separating Words in Ansible

Prefix public role variables with the role name, internal role variables with __role_name_, inventory-only variables with a project prefix, and separate words with underscores in Ansible variable names.

Problem

Without a clear naming rule it is not always obvious whether a variable belongs to a role’s public interface, to role-internal logic, or only to the inventory project.

That increases the chance of collisions, accidental overrides, and unclear role contracts. Merged words such as iis_apppools also make names harder to scan than separated forms such as iis_web_app_pools.

Context

Ansible variables are not namespaced per role or per inventory file. Variables from roles, group_vars, host_vars, registered results, and task-level vars all share the same runtime namespace.

The Ansible documentation allows only letters, numbers, and underscores in variable names. In practice, ansible-lint narrows this further to lowercase alphanumeric names with underscores and expects role variables inside roles to use the role-name prefix.

The Red Hat CoP guidance recommends prefixing public role variables with the role name and internal variables with a double underscore. In that guidance, internal variables explicitly include values created with register and set_fact, because they also remain in the runtime namespace after the role has finished.1 The double underscore is only a naming convention, not privacy or security.2

Inventory-only project variables still need their own clear project prefix, such as c2_ or gs_.

Solution

Decision rule: if a variable is not intended to be set, overridden, or consumed by role users through inventory or role parameters, treat it as internal and prefix it with __role_name_.

  1. Prefix every public role variable with the role name, for example haproxy_frontends.
  2. Prefix every internal role variable with __role_name_, for example __haproxy_frontends_rendered.
  3. Prefix every inventory-only project variable that exists only in group_vars or host_vars with a project prefix, for example c2_cacerts2_ca_dir.
  4. Write multiword variable names in lowercase snake case and separate words with underscores.
  5. Follow module or domain terminology when it helps clarify the contract, for example prefer iis_web_app_pools over iis_apppools.
  6. Use documented upstream ansible_* variables only for Ansible-defined settings such as ansible_become_user.
  7. If your project enforces a custom ansible-lint naming pattern, extend it to accept both public and internal role variable forms.

Benefits

  • Makes variable ownership and scope visible.
  • Distinguishes supported role inputs from internal helper variables.
  • Reduces collisions across roles, inventory, and temporary variables.
  • Makes names easier to scan because word boundaries stay explicit.
  • Helps editor spell checking flag suspicious words more clearly.3

Examples and Implementation

Public, internal, and project variables

# defaults/main.yml
haproxy_frontends: []

# task vars or set_fact targets
__haproxy_frontends_rendered: "{{ haproxy_frontends }}"

# group_vars/all/smallca.yml
c2_cacerts2_ca_dir: /etc/pki/ca-trust/source/anchors

Internal variables from register and set_fact

- name: Read HAProxy service state
  ansible.builtin.command: systemctl is-active haproxy
  register: __haproxy_service_state
  changed_when: false

- name: Derive desired backend list
  ansible.builtin.set_fact:
    __haproxy_backends_effective: >-
      {{ haproxy_backends
      | selectattr('enabled', 'equalto', true)
      | list }}

These values are internal when they support the role implementation and are not part of the supported interface for users overriding the role.

Custom loop_var names can still be internal

For general guidance on when to keep item and when to introduce a custom loop_var, see Using item and loop_var in Ansible Loops .

When a custom loop_var crosses an include_tasks boundary or would otherwise look like a supported role input, prefer the internal form.

- name: Configure IIS web application pools
  ansible.builtin.include_tasks: web_app_pool.yml
  loop: "{{ iis_web_app_pools }}"
  loop_control:
    loop_var: __iis_web_app_pool
    label: "{{ __iis_web_app_pool['name'] }}"
# web_app_pool.yml
- name: Ensure IIS web application pool exists
  microsoft.iis.web_app_pool:
    name: "{{ __iis_web_app_pool['name'] }}"
    state: "{{ __iis_web_app_pool['state'] | default(omit) }}"

A name such as iis_web_app_pool is technically valid, but __iis_web_app_pool more clearly signals that the value is an internal helper passed between task files, not a public role variable.4

Separate words explicitly

iis_web_app_pools:
  - name: HelloWorld
    state: started

Avoid merged forms such as:

iis_apppools:
  - name: HelloWorld
    state: started

The separated form is clearer because it matches normal snake case, mirrors the module term web_app_pool from microsoft.iis.web_app_pool, and makes it obvious that the variable stores multiple resources.

ansible-lint pattern for stricter projects

# markdownlint-disable-next-line MD013
var_naming_pattern: "^(__)?(haproxy|guacamole|ansible)_[a-z0-9_]*$|^ansible_[a-z0-9_]*$"

This pattern allows public variables such as haproxy_frontends, internal variables such as __haproxy_frontends_rendered, and documented upstream variables such as ansible_become_user.

Additional Information


  1. Red Hat CoP is the clearest source for this convention. It explicitly applies the internal-variable rule to values created with register and set_fact, because Ansible variables are not local to a role in the way many readers expect. ↩︎

  2. Ansible allows variable names that begin with an underscore, but it does not treat them as private. See

    Using variables — Ansible Community Documentation  ↩︎

  3. In editors with spell checking enabled, separated words are more likely to be checked individually. In practice this makes typos easier to spot visually, for example as a colored underline in VS Code depending on the user’s spell-check setup. ↩︎

  4. Ansible documents loop_control.loop_var and the special variable ansible_loop_var for loop handling. The opt-in ansible-lint loop-var-prefix rule also supports conventions such as ^(__|{role}_), which fits the idea that helper loop variables crossing task-file boundaries may use the internal __ form. ↩︎


Last modified September 17, 2026: guideline prefixing variables C2-1637 (1a74124)