While automation exists to make it easier to make things repeatable, all systems are not exactly alike; some may require configuration that is slightly different from others. In some instances, the observed behavior or state of one system might influence how you configure other systems. For example, you might need to find out the IP address of a system and use it as a configuration value on another system.

Ansible uses variables to help deal with differences between systems.

To understand variables you’ll also want to read Conditionals and Loops.
Useful things like the group_by module
and the when conditional can also be used with variables, and to help manage differences between systems.

The ansible-examples github repository contains many examples of how variables are used in Ansible.

Before we start using variables, it’s important to know what are valid variable names.

Variable names should be letters, numbers, and underscores. Variables should always start with a letter.

foo_port is a great variable. foo5 is fine too.

foo-port, fooport, foo.port and 12 are not valid variable names.

YAML also supports dictionaries which map keys to values. For instance:

foo:field1:onefield2:two

You can then reference a specific field in the dictionary using either bracket
notation or dot notation:

foo['field1']foo.field1

These will both reference the same value (“one”). However, if you choose to
use dot notation be aware that some keys can cause problems because they
collide with attributes and methods of python dictionaries. You should use
bracket notation instead of dot notation if you use keys which start and end
with two underscores (Those are reserved for special meanings in python) or
are any of the known public attributes:

It turns out we’ve already talked about variables in another place too.

As described in Roles, variables can also be included in the playbook via include files, which may or may
not be part of an “Ansible Role”. Usage of roles is preferred as it provides a nice organizational system.

It’s nice enough to know about how to define variables, but how do you use them?

Ansible allows you to reference variables in your playbooks using the Jinja2 templating system. While you can do a lot of complex things in Jinja, only the basics are things you really need to learn at first.

For example, in a simple template, you can do something like:

My amp goes to{{max_amp_value}}

And that will provide the most basic form of variable substitution.

This is also valid directly in playbooks, and you’ll occasionally want to do things like:

template:src=foo.cfg.j2 dest={{remote_install_path}}/foo.cfg

In the above example, we used a variable to help decide where to place a file.

Inside a template you automatically have access to all of the variables that are in scope for a host. Actually
it’s more than that – you can also read variables about other hosts. We’ll show how to do that in a bit.

Note

ansible allows Jinja2 loops and conditionals in templates, but in playbooks, we do not use them. Ansible
playbooks are pure machine-parseable YAML. This is a rather important feature as it means it is possible to code-generate
pieces of files, or to have other ecosystem tools read Ansible files. Not everyone will need this but it can unlock
possibilities.

YAML syntax requires that if you start a value with {{foo}} you quote the whole line, since it wants to be
sure you aren’t trying to start a YAML dictionary. This is covered on the YAML Syntax documentation.

In the above the model of the first harddrive may be referenced in a template or playbook as:

{{ansible_devices.sda.model}}

Similarly, the hostname as the system reports it is:

{{ansible_nodename}}

and the unqualified hostname shows the string before the first period(.):

{{ansible_hostname}}

Facts are frequently used in conditionals (see Conditionals) and also in templates.

Facts can be also used to create dynamic groups of hosts that match particular criteria, see the Working With Modules documentation on group_by for details, as well as in generalized conditional statements as discussed in the Conditionals chapter.

If you know you don’t need any fact data about your hosts, and know everything about your systems centrally, you
can turn off fact gathering. This has advantages in scaling Ansible in push mode with very large numbers of
systems, mainly, or if you are using Ansible on experimental platforms. In any play, just do this:

As discussed in the playbooks chapter, Ansible facts are a way of getting data about remote systems for use in playbook variables.

Usually these are discovered automatically by the setup module in Ansible. Users can also write custom facts modules, as described in the API guide. However, what if you want to have a simple way to provide system or user provided data for use in Ansible variables, without writing a fact module?

“Facts.d” is one mechanism for users to control some aspect of how their systems are managed.

Note

Perhaps “local facts” is a bit of a misnomer, it means “locally supplied user values” as opposed to “centrally supplied user values”, or what facts are – “locally dynamically determined values”.

If a remotely managed system has an /etc/ansible/facts.d directory, any files in this directory
ending in .fact, can be JSON, INI, or executable files returning JSON, and these can supply local facts in Ansible.
An alternate directory can be specified using the fact_path play keyword.

For example, assume /etc/ansible/facts.d/preferences.fact contains:

[general]asdf=1bar=2

This will produce a hash variable fact named general with asdf and bar as members.
To validate this, run the following:

The local namespace prevents any user supplied fact from overriding system facts or variables defined elsewhere in the playbook.

Note

The key part in the key=value pairs will be converted into lowercase inside the ansible_local variable. Using the example above, if the ini file contained XYZ=3 in the [general] section, then you should expect to access it as: {{ansible_local.preferences.general.xyz}} and not {{ansible_local.preferences.general.XYZ}}. This is because Ansible uses Python’s ConfigParser which passes all option names through the optionxform method and this method’s default implementation converts option names to lower case.

If you have a playbook that is copying over a custom fact and then running it, making an explicit call to re-run the setup module
can allow that fact to be used during that particular play. Otherwise, it will be available in the next play that gathers fact information.
Here is an example of what that might look like:

As shown elsewhere in the docs, it is possible for one server to reference variables about another, like so:

{{hostvars['asdf.example.com']['ansible_os_family']}}

With “Fact Caching” disabled, in order to do this, Ansible must have already talked to ‘asdf.example.com’ in the
current play, or another play up higher in the playbook. This is the default configuration of ansible.

To avoid this, Ansible 1.8 allows the ability to save facts between playbook runs, but this feature must be manually
enabled. Why might this be useful?

With a very large infrastructure with thousands of hosts, fact caching could be configured to run nightly. Configuration of a small set of servers could run ad-hoc or periodically throughout the day. With fact caching enabled, it would
not be necessary to “hit” all servers to reference variables and information about them.

With fact caching enabled, it is possible for machine in one group to reference variables about machines in the other group, despite the fact that they have not been communicated with in the current execution of /usr/bin/ansible-playbook.

To benefit from cached facts, you will want to change the gathering setting to smart or explicit or set gather_facts to False in most plays.

Another major use of variables is running a command and using the result of that command to save the result into a variable. Results will vary from module to module. Use of -v when executing playbooks will show possible values for the results.

The value of a task being executed in ansible can be saved in a variable and used later. See some examples of this in the
Conditionals chapter.

While it’s mentioned elsewhere in that document too, here’s a quick syntax example:

Registered variables are valid on the host the remainder of the playbook run, which is the same as the lifetime of “facts”
in Ansible. Effectively registered variables are just like facts.

When using register with a loop, the data structure placed in the variable during the loop will contain a results attribute, that is a list of all responses from the module. For a more in-depth example of how this works, see the Loops section on using register with a loop.

Note

If a task fails or is skipped, the variable still is registered with a failure or skipped status, the only way to avoid registering a variable is using tags.

Some provided facts, like networking information, are made available as nested data structures. To access
them a simple {{foo}} is not sufficient, but it is still easy to do. Here’s how we get an IP address:

Even if you didn’t define them yourself, Ansible provides a few variables for you automatically.
The most important of these are hostvars, group_names, and groups. Users should not use
these names themselves as they are reserved. environment is also reserved.

hostvars lets you ask about the variables of another host, including facts that have been gathered
about that host. If, at this point, you haven’t talked to that host yet in any play in the playbook
or set of playbooks, you can still get the variables, but you will not be able to see the facts.

If your database server wants to use the value of a ‘fact’ from another node, or an inventory variable
assigned to another node, it’s easy to do so within a template or even an action line:

{{hostvars['test.example.com']['ansible_distribution']}}

Additionally, group_names is a list (array) of all the groups the current host is in. This can be used in templates using Jinja2 syntax to make template source files that vary based on the group membership (or role) of the host

{%if'webserver'ingroup_names%} # some part of a configuration file that only applies to webservers{%endif%}

groups is a list of all the groups (and hosts) in the inventory. This can be used to enumerate all hosts within a group.
For example:

{%forhostingroups['app_servers']%} # something that applies to all app servers.{%endfor%}

A frequently used idiom is walking a group to find all IP addresses in that group

An example of this could include pointing a frontend proxy server to all of the app servers, setting up the correct firewall rules between servers, etc.
You need to make sure that the facts of those hosts have been populated before though, for example by running a play against them if the facts have not been cached recently (fact caching was added in Ansible 1.8).

Additionally, inventory_hostname is the name of the hostname as configured in Ansible’s inventory host file. This can
be useful for when you don’t want to rely on the discovered hostname ansible_hostname or for other mysterious
reasons. If you have a long FQDN, inventory_hostname_short also contains the part up to the first
period, without the rest of the domain.

play_hosts has been deprecated in 2.2, it was the same as the new ansible_play_batch variable.

New in version 2.2.

ansible_play_hosts is the full list of all hosts still active in the current play.

New in version 2.2.

ansible_play_batch is available as a list of hostnames that are in scope for the current ‘batch’ of the play. The batch size is defined by serial, when not set it is equivalent to the whole play (making it the same as ansible_play_hosts).

New in version 2.3.

ansible_playbook_python is the path to the python executable used to invoke the Ansible command line tool.

These vars may be useful for filling out templates with multiple hostnames or for injecting the list into the rules for a load balancer.

Don’t worry about any of this unless you think you need it. You’ll know when you do.

Also available, inventory_dir is the pathname of the directory holding Ansible’s inventory host file, inventory_file is the pathname and the filename pointing to the Ansible’s inventory host file.

playbook_dir contains the playbook base directory.

We then have role_path which will return the current role’s pathname (since 1.8). This will only work inside a role.

And finally, ansible_check_mode (added in version 2.1), a boolean magic variable which will be set to True if you run Ansible with --check.

It’s a great idea to keep your playbooks under source control, but
you may wish to make the playbook source public while keeping certain
important variables private. Similarly, sometimes you may just
want to keep certain information in different files, away from
the main playbook.

You can do this by using an external variables file, or files, just like this:

----hosts:allremote_user:rootvars:favcolor:bluevars_files:-/vars/external_vars.ymltasks:-name:this is just a placeholdercommand:/bin/echo foo

This removes the risk of sharing sensitive data with others when
sharing your playbook source with them.

The contents of each variables file is a simple YAML dictionary, like this:

---# in the above example, this would be vars/external_vars.ymlsomevar:somevaluepassword:magic

In addition to vars_prompt and vars_files, it is possible to set variables at the
command line using the --extra-vars (or -e) argument. Variables can be defined using
a single quoted string (containing one or more variables) using one of the formats below

Basically, anything that goes into “role defaults” (the defaults folder inside the role) is the most malleable and easily overridden. Anything in the vars directory of the role overrides previous versions of that variable in namespace. The idea here to follow is that the more explicit you get in scope, the more precedence it takes with command line -e extra vars always winning. Host and/or inventory variables can win over role defaults, but not explicit includes like the vars directory or an include_vars task.

When created with set_facts’s cacheable option, variables will have the high precedence in the play,
but will be the same as a host facts precedence when they come from the cache.

Note

Within any section, redefining a var will overwrite the previous instance.
If multiple groups have the same variable, the last one loaded wins.
If you define a variable twice in a play’s vars: section, the second one wins.

Note

The previous describes the default config hash_behaviour=replace, switch to merge to only partially overwrite.

Note

Group loading follows parent/child relationships. Groups of the same ‘patent/child’ level are then merged following alphabetical order.
This last one can be superceeded by the user via ansible_group_priority, which defaults to 1 for all groups.

Another important thing to consider (for all versions) is that connection variables override config, command line and play/role/task specific options and keywords. For example, if your inventory specifies ansible_ssh_user:ramon and you run:

ansible -u lola myhost

This will still connect as ramon because the value from the variable takes priority (in this case, the variable came from the inventory, but the same would be true no matter where the variable was defined).

For plays/tasks this is also true for remote_user. Assuming the same inventory config, the following play:

Let’s show some examples and where you would choose to put what based on the kind of control you might want over values.

First off, group variables are powerful.

Site wide defaults should be defined as a group_vars/all setting. Group variables are generally placed alongside
your inventory file. They can also be returned by a dynamic inventory script (see Working With Dynamic Inventory) or defined
in things like Ansible Tower from the UI or API:

---# file: /etc/ansible/group_vars/all# this is the site wide defaultntp_server:default-time.example.com

Regional information might be defined in a group_vars/region variable. If this group is a child of the all group (which it is, because all groups are), it will override the group that is higher up and more general:

So that covers inventory and what you would normally set there. It’s a great place for things that deal with geography or behavior. Since groups are frequently the entity that maps roles onto hosts, it is sometimes a shortcut to set variables on the group instead of defining them on a role. You could go either way.

We’ll pretty much assume you are using roles at this point. You should be using roles for sure. Roles are great. You are using
roles aren’t you? Hint hint.

If you are writing a redistributable role with reasonable defaults, put those in the roles/x/defaults/main.yml file. This means
the role will bring along a default value but ANYTHING in Ansible will override it.
See Roles for more info about this:

---# file: roles/x/defaults/main.yml# if not overridden in inventory or as a parameter, this is the value that will be usedhttp_port:80

If you are writing a role and want to ensure the value in the role is absolutely used in that role, and is not going to be overridden
by inventory, you should put it in roles/x/vars/main.yml like so, and inventory values cannot override it. -e however, still will:

---# file: roles/x/vars/main.yml# this will absolutely be used in this rolehttp_port:80

This is one way to plug in constants about the role that are always true. If you are not sharing your role with others,
app specific behaviors like ports is fine to put in here. But if you are sharing roles with others, putting variables in here might
be bad. Nobody will be able to override them with inventory, but they still can by passing a parameter to the role.

Parameterized roles are useful.

If you are using a role and want to override a default, pass it as a parameter to the role like so:

roles:-role:apachevars:http_port:8080

This makes it clear to the playbook reader that you’ve made a conscious choice to override some default in the role, or pass in some
configuration that the role can’t assume by itself. It also allows you to pass something site-specific that isn’t really part of the
role you are sharing with others.

This can often be used for things that might apply to some hosts multiple times. For example:

In this example, the same role was invoked multiple times. It’s quite likely there was
no default for name supplied at all. Ansible can warn you when variables aren’t defined – it’s the default behavior in fact.

There are a few other things that go on with roles.

Generally speaking, variables set in one role are available to others. This means if you have a roles/common/vars/main.yml you
can set variables in there and make use of them in other roles and elsewhere in your playbook:

There are some protections in place to avoid the need to namespace variables.
In the above, variables defined in common_settings are most definitely available to ‘something’ and ‘something_else’ tasks, but if
“something’s” guaranteed to have foo set at 12, even if somewhere deep in common settings it set foo to 20.

So, that’s precedence, explained in a more direct way. Don’t worry about precedence, just think about if your role is defining a
variable that is a default, or a “live” variable you definitely want to use. Inventory lies in precedence right in the middle, and
if you want to forcibly override something, use -e.

If you found that a little hard to understand, take a look at the ansible-examples repo on our github for a bit more about
how all of these things can work together.