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:

  1. Role defaults (lowest)

  2. Inventory variables

  3. Group variables from inventory

  4. Host variables from inventory

  5. Group vars files (group_vars/*)

  6. Host vars files (host_vars/*)

  7. Play variables (vars: in playbook)

  8. Task variables

  9. Include variables

  10. 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:

  1. 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'
  2. Registry Values: Store registry paths in variables

    app_registry_path: HKLM:\SOFTWARE\MyApp
  3. 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:

  1. Organize thoughtfully: Choose the right location for variables based on their purpose and scope

  2. Use the right data structures: Lists, dictionaries, and simple variables all have their place

  3. Understand precedence: Know which variables will override others

  4. Protect secrets: Use Ansible Vault for sensitive data

  5. 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