Variables Usage
Last updated: June 29, 2025
My Variable Journey with Ansible
When I first started automating with Ansible, I used to hardcode values directly in my playbooks - server names, file paths, configuration options - everything was fixed. This approach quickly became unwieldy as I started managing more complex environments. The real "aha" moment came when I fully embraced variables in Ansible, which transformed my playbooks from rigid, single-purpose scripts into flexible, reusable automation tools.
In this blog post, I'll share my experience with Ansible variables - what they are, how to use them effectively, and how they've helped me automate both Linux and Windows environments more efficiently. Whether you're managing a handful of systems or a complex multi-environment infrastructure, mastering variables is essential to your Ansible journey.
Understanding Variables in Ansible
Variables in Ansible are containers that store values which can differ between hosts, environments, or runs. They allow you to create playbooks that can adapt to different scenarios without requiring code changes. Think of them as the "dynamic" parts of your otherwise static playbooks.
Valid Variable Names
Before diving into variable usage, it's important to understand the naming rules:
Variables can contain letters, numbers, and underscores
They must start with a letter or underscore (never a number)
Never use spaces or dashes in variable names
Avoid using Python or Ansible reserved keywords
Good variable names:
server_name
http_port
web_user
_internal_variable
max_connections
Invalid variable names:
5server (starts with a number)
server-name (contains dash)
async (Python keyword)
environment (Ansible keyword)
Where to Define Variables
One of the most powerful aspects of Ansible is the flexibility in where and how you can define variables. Over the years, I've learned that choosing the right location depends on your specific use case.
In Playbooks
The simplest approach is defining variables directly in playbooks:
---
- hosts: webservers
vars:
http_port: 80
max_clients: 200
document_root: /var/www/html
tasks:
- name: Configure Apache
template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
This works well for small playbooks, but as your automation grows, you'll want more organization.
In Variable Files
For better organization, I prefer keeping variables in separate files:
---
- hosts: webservers
vars_files:
- vars/apache_config.yml
tasks:
- name: Configure Apache
template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
With the contents of vars/apache_config.yml
:
---
http_port: 80
max_clients: 200
document_root: /var/www/html
This approach makes your playbooks cleaner and allows for easier reuse of common variable sets.
In Inventory
I often use inventory variables for host-specific or group-specific settings. This works well for values that are tied to specific hosts or environments:
# In inventory file (INI format)
[webservers]
web1.example.com http_port=80 max_clients=200
web2.example.com http_port=8080 max_clients=100
[webservers:vars]
document_root=/var/www/html
Using host_vars and group_vars Directories
For larger environments, I organize variables into host_vars
and group_vars
directories. This approach has saved me countless hours of maintenance:
inventory/
hosts
group_vars/
all.yml # Variables for all hosts
webservers.yml # Variables for webserver group
dbservers.yml # Variables for database group
host_vars/
web1.example.com.yml # Specific to web1
db1.example.com.yml # Specific to db1
Contents of group_vars/webservers.yml
:
---
http_port: 80
max_clients: 200
document_root: /var/www/html
Contents of host_vars/web1.example.com.yml
:
---
http_port: 8080 # Override the group variable for this specific host
Variable Types in Ansible
Ansible supports various variable types to represent different kinds of data. Understanding them has helped me build more effective playbooks.
Simple Variables
These are basic key-value pairs:
app_name: inventory-system
version: 2.5.1
debug_mode: true
max_connections: 100
List Variables
For storing multiple values in an ordered sequence:
# Define a list
allowed_ips:
- 192.168.1.100
- 192.168.1.101
- 192.168.1.102
# Alternative syntax
allowed_ips: [192.168.1.100, 192.168.1.101, 192.168.1.102]
To reference items in a list:
first_ip: "{{ allowed_ips[0] }}" # Gets 192.168.1.100
Dictionary Variables
For storing structured data with keys and values:
database:
host: db.example.com
port: 5432
name: inventory
user: app_user
max_connections: 100
To reference values in a dictionary:
# Using dot notation
db_host: "{{ database.host }}"
# Using bracket notation (more compatible)
db_port: "{{ database['port'] }}"
Using Variables in Playbooks
Once defined, variables can be used throughout your playbooks, templates, and conditionals. The proper syntax for referencing variables is {{ variable_name }}
.
In Tasks
- name: Create configuration file
template:
src: app.conf.j2
dest: "/etc/{{ app_name }}/config.conf"
owner: "{{ app_user }}"
mode: '0644'
In Conditionals
- name: Start service in production mode
service:
name: "{{ app_name }}"
state: started
enabled: yes
when: environment_type == "production"
In Loops
- name: Create user accounts
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'john', groups: 'users,admin' }
- { name: 'jane', groups: 'users' }
Windows Example: Managing IIS with Variables
For Windows environments, variables work the same way but are used with Windows-specific modules. Here's how I manage IIS configurations using variables:
---
- hosts: windows_servers
vars:
iis_sites:
- name: Default Web Site
state: started
port: 80
path: C:\inetpub\wwwroot
- name: Intranet
state: started
port: 8080
path: D:\websites\intranet
tasks:
- name: Ensure IIS web sites are configured
win_iis_website:
name: "{{ item.name }}"
state: "{{ item.state }}"
physical_path: "{{ item.path }}"
port: "{{ item.port }}"
loop: "{{ iis_sites }}"
Registering Variables
One of the most powerful features I've found is the ability to capture output from tasks using the register
keyword:
- name: Check service status
command: systemctl status nginx
register: service_status
ignore_errors: true
- name: Debug service status
debug:
msg: "Service is {{ 'running' if service_status.rc == 0 else 'stopped' }}"
For Windows, a similar approach works:
- name: Check if file exists
win_stat:
path: C:\Program Files\MyApp\config.ini
register: config_file
- name: Create config file if missing
win_copy:
src: templates/config.ini
dest: C:\Program Files\MyApp\config.ini
when: not config_file.stat.exists
Variable Precedence
Understanding variable precedence has saved me from many debugging sessions. Ansible loads all possible variables but applies them in a specific order. Here's a simplified version from lowest to highest precedence:
Role defaults (lowest)
Inventory variables
Group variables from inventory
Host variables from inventory
Group vars files (group_vars/*)
Host vars files (host_vars/*)
Play variables (vars: in playbook)
Task variables
Include variables
Extra variables (highest, command line -e)
Sequence Diagram: Variable Processing Flow
Here's a visual representation of how Ansible processes variables:
Managing Secret Variables with Ansible Vault
For sensitive information like passwords and API keys, I always use Ansible Vault. This allows me to encrypt variables while still using them in playbooks:
# Encrypt a variable file
ansible-vault encrypt vars/secure_vars.yml
# Use encrypted variables in a playbook
ansible-playbook site.yml --ask-vault-pass
Inside vars/secure_vars.yml
(before encryption):
---
db_password: supersecret123
api_key: ABCDEF123456789
Environment-Specific Variables
To manage multiple environments (dev, staging, production), I use a combination of group variables and variable files:
---
- hosts: all
vars:
common_settings:
app_name: inventory-app
log_level: info
vars_files:
- "vars/{{ environment_type }}_settings.yml"
tasks:
- name: Configure application
template:
src: app.conf.j2
dest: "/etc/{{ common_settings.app_name }}/config.conf"
I would then run this with:
ansible-playbook site.yml -e "environment_type=production"
Variable Tips for Complex Environments
After years of working with Ansible, here are some practical variable tips I've learned:
1. Use Default Values
Provide defaults to prevent undefined variable errors:
database_port: "{{ custom_db_port | default(5432) }}"
2. Combine Variables
For complex data structures, you can combine variables:
- name: Combine dictionaries
set_fact:
server_config: "{{ default_config | combine(environment_config) }}"
3. Namespace Your Variables
For larger projects, use namespacing to avoid collisions:
app_database_host: db.example.com # Instead of just "host"
app_cache_host: cache.example.com # Instead of conflicting "host" variable
4. Debug Variables When Needed
- name: Debug variable value
debug:
var: complex_variable
verbosity: 1 # Only show when -v is used
5. Use Variable Files for Different OS Types
- name: Include OS-specific variables
include_vars: "{{ ansible_os_family }}.yml"
With separate files like Debian.yml
and Windows.yml
containing OS-specific variables.
Windows-Specific Variable Notes
When working with Windows hosts, I've found these variable practices helpful:
Path Formatting: Use forward slashes or escaped backslashes
# These both work in templates: win_path: 'C:/Program Files/App' # or win_path: 'C:\\Program Files\\App'
Registry Values: Store registry paths in variables
app_registry_path: HKLM:\SOFTWARE\MyApp
Windows Features: Maintain lists of required features
required_features: - Web-Server - Web-Asp-Net45 - NET-Framework-45-Core
Conclusion
Variables are what transform Ansible from a simple command runner into a powerful automation platform. As my infrastructure has grown more complex, I've come to rely heavily on the variable system to make my playbooks adaptable and reusable across environments.
The key lessons I've learned about Ansible variables are:
Organize thoughtfully: Choose the right location for variables based on their purpose and scope
Use the right data structures: Lists, dictionaries, and simple variables all have their place
Understand precedence: Know which variables will override others
Protect secrets: Use Ansible Vault for sensitive data
Provide defaults: Make playbooks more robust by handling missing variables gracefully
Whether you're managing Linux web servers, Windows application servers, or a complex hybrid environment, mastering variables will dramatically increase your productivity with Ansible. It's been one of the most valuable skills in my automation toolkit.
Last updated