
How to Use Variables in Ansible Playbooks
How to Use Variables in Ansible Playbooks
Variables in Ansible playbooks are fundamental building blocks that make your automation scripts flexible, reusable, and maintainable. Instead of hardcoding values throughout your playbooks, variables allow you to define configuration data once and reference it multiple times, making it easier to manage different environments, server configurations, and deployment scenarios. This guide will walk you through the various ways to define, use, and manage variables in Ansible, from basic string substitution to complex data structures and advanced templating techniques.
Understanding Ansible Variable Types and Scope
Ansible variables come in several flavors, each with different scope and precedence levels. The most common types include simple strings, numbers, booleans, lists, and dictionaries. Variables can be defined at multiple levels – from global inventory variables down to task-specific vars.
The variable precedence order (from lowest to highest) is crucial to understand:
- command line values (for example, -u my_user, these are not variables)
- role defaults (defined in role/defaults/main.yml)
- inventory file or script group vars
- inventory group_vars/all
- playbook group_vars/all
- inventory group_vars/*
- playbook group_vars/*
- inventory file or script host vars
- inventory host_vars/*
- playbook host_vars/*
- host facts / cached set_facts
- play vars
- play vars_prompt
- play vars_files
- role vars (defined in role/vars/main.yml)
- block vars (only for tasks in block)
- task vars (only for the task)
- include_vars
- set_facts / registered vars
- role (and include_role) params
- include params
- extra vars (for example, -e “user=my_user”)(always win precedence)
Step-by-Step Variable Implementation Guide
Basic Variable Definition
The simplest way to define variables is directly in your playbook using the `vars` section:
---
- hosts: webservers
vars:
http_port: 80
max_clients: 200
server_name: "{{ ansible_hostname }}"
packages:
- nginx
- php-fpm
- mysql-server
tasks:
- name: Install packages
package:
name: "{{ packages }}"
state: present
- name: Configure nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
vars:
worker_processes: 4
keepalive_timeout: 65
Using Variable Files
For better organization, separate your variables into external files:
# vars/main.yml
database:
host: localhost
port: 3306
name: myapp
user: dbuser
password: "{{ vault_db_password }}"
application:
debug: false
secret_key: "{{ vault_app_secret }}"
allowed_hosts:
- "{{ ansible_default_ipv4.address }}"
- localhost
- 127.0.0.1
nginx:
worker_processes: "{{ ansible_processor_vcpus }}"
worker_connections: 1024
client_max_body_size: 64M
Then include it in your playbook:
---
- hosts: all
vars_files:
- vars/main.yml
- vars/secrets.yml
tasks:
- name: Create database
mysql_db:
name: "{{ database.name }}"
state: present
delegate_to: "{{ database.host }}"
Inventory Variables
Define variables in your inventory file for host or group-specific configurations:
# inventory/hosts
[webservers]
web1.example.com http_port=80 ssl_port=443
web2.example.com http_port=8080 ssl_port=8443
[webservers:vars]
nginx_user=www-data
document_root=/var/www/html
[databases]
db1.example.com mysql_port=3306
db2.example.com mysql_port=3307
[production:children]
webservers
databases
[production:vars]
environment=prod
backup_enabled=true
monitoring=true
Advanced Variable Techniques
Using Facts as Variables
Ansible automatically gathers system facts that can be used as variables:
- name: Display system information
debug:
msg: |
Hostname: {{ ansible_hostname }}
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
Architecture: {{ ansible_architecture }}
Memory: {{ ansible_memtotal_mb }}MB
CPU cores: {{ ansible_processor_vcpus }}
IP Address: {{ ansible_default_ipv4.address }}
Registered Variables
Capture command output and use it in subsequent tasks:
- name: Check if service exists
command: systemctl list-unit-files nginx.service
register: nginx_service_check
failed_when: false
changed_when: false
- name: Install nginx if not present
package:
name: nginx
state: present
when: "'nginx.service' not in nginx_service_check.stdout"
- name: Get current nginx version
shell: nginx -v 2>&1 | cut -d'/' -f2
register: nginx_version
changed_when: false
- name: Display nginx version
debug:
msg: "Nginx version: {{ nginx_version.stdout }}"
Real-World Use Cases and Examples
Multi-Environment Deployment
Structure your variables for different environments:
# group_vars/development.yml
database_host: dev-db.internal
redis_host: dev-redis.internal
debug_mode: true
log_level: debug
ssl_enabled: false
# group_vars/staging.yml
database_host: staging-db.internal
redis_host: staging-redis.internal
debug_mode: false
log_level: info
ssl_enabled: true
# group_vars/production.yml
database_host: prod-db.internal
redis_host: prod-redis.internal
debug_mode: false
log_level: warning
ssl_enabled: true
backup_retention_days: 30
Dynamic Configuration Generation
Use complex variable structures for configuration templates:
nginx_vhosts:
- name: api.example.com
document_root: /var/www/api
ssl:
enabled: true
cert_file: /etc/ssl/certs/api.example.com.crt
key_file: /etc/ssl/private/api.example.com.key
upstream:
- server: 127.0.0.1:8001
weight: 1
- server: 127.0.0.1:8002
weight: 1
- name: admin.example.com
document_root: /var/www/admin
ssl:
enabled: true
cert_file: /etc/ssl/certs/admin.example.com.crt
key_file: /etc/ssl/private/admin.example.com.key
auth:
enabled: true
users:
- username: admin
password: "{{ vault_admin_password }}"
Conditional Logic with Variables
- name: Configure firewall based on environment
ufw:
rule: allow
port: "{{ item.port }}"
src: "{{ item.src | default('any') }}"
loop:
- port: 22
src: "{{ management_network | default('any') }}"
- port: 80
- port: 443
when:
- firewall_enabled | default(true)
- environment != 'development'
- name: Install development tools
package:
name: "{{ dev_packages }}"
state: present
vars:
dev_packages:
- git
- vim
- htop
- curl
- wget
when: environment == 'development'
Variable Comparison and Best Practices
Method | Scope | Best Use Case | Pros | Cons |
---|---|---|---|---|
Playbook vars | Play level | Simple, static values | Easy to read, self-contained | Not reusable, limited flexibility |
vars_files | Play level | Shared configuration | Reusable, organized | File management overhead |
group_vars | Group level | Environment-specific settings | Automatic loading, scalable | Directory structure dependency |
host_vars | Host level | Host-specific configuration | Precise control | Can become unwieldy |
Extra vars (-e) | Global | Runtime overrides | Highest precedence | Command line only |
Common Pitfalls and Troubleshooting
Variable Naming Issues
Avoid these common naming problems:
# Bad: Reserved keywords and special characters
- hosts: all
vars:
class: production # 'class' is reserved in Python
user-name: admin # Hyphens cause issues
2nd_server: web2 # Starting with numbers
# Good: Clean, descriptive names
- hosts: all
vars:
server_class: production
username: admin
secondary_server: web2
Template Syntax Errors
Common Jinja2 templating mistakes:
# Incorrect: Missing quotes around templated values
- name: Set file permissions
file:
path: /tmp/test
mode: {{ file_mode }} # This will fail
# Correct: Proper quoting
- name: Set file permissions
file:
path: /tmp/test
mode: "{{ file_mode }}"
# Incorrect: Complex logic in templates
- name: Complex conditional
debug:
msg: "{{ 'production' if environment == 'prod' and ssl_enabled == true and port == 443 else 'development' }}"
# Better: Use when conditions
- name: Production message
debug:
msg: "Running in production mode"
when:
- environment == 'prod'
- ssl_enabled | bool
- port == 443
Debugging Variables
Use these techniques to troubleshoot variable issues:
- name: Debug all variables for current host
debug:
var: hostvars[inventory_hostname]
- name: Debug specific variable
debug:
var: database
verbosity: 1
- name: Check variable type and content
debug:
msg: |
Variable name: packages
Type: {{ packages | type_debug }}
Content: {{ packages }}
Length: {{ packages | length }}
- name: Show gathered facts
setup:
register: host_facts
- name: Display facts
debug:
var: host_facts.ansible_facts
verbosity: 2
Security Considerations with Ansible Vault
Never store sensitive data in plain text. Use Ansible Vault for passwords, API keys, and certificates:
# Create encrypted variable file
ansible-vault create vars/secrets.yml
# Edit encrypted file
ansible-vault edit vars/secrets.yml
# Example encrypted variables file content:
vault_database_password: supersecretpassword
vault_api_key: abc123def456
vault_ssl_private_key: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
Reference vaulted variables in playbooks:
- name: Configure database connection
template:
src: database.conf.j2
dest: /etc/myapp/database.conf
mode: '0600'
vars:
db_password: "{{ vault_database_password }}"
Run playbooks with vault password:
ansible-playbook -i inventory playbook.yml --ask-vault-pass
# or
ansible-playbook -i inventory playbook.yml --vault-password-file ~/.vault_pass
## Performance Optimization
### Variable Caching and Facts
Control fact gathering for better performance:
---
- hosts: all
gather_facts: false # Skip automatic fact gathering
tasks:
- name: Gather minimal facts
setup:
filter:
- 'ansible_distribution*'
- 'ansible_hostname'
- 'ansible_default_ipv4'
when: custom_facts_needed | default(false)
Efficient Variable Loading
# Load variables conditionally
- name: Load environment-specific variables
include_vars: "{{ item }}"
with_first_found:
- "vars/{{ environment }}.yml"
- "vars/default.yml"
# Cache expensive operations
- name: Get service status
command: systemctl is-active nginx
register: nginx_status
changed_when: false
run_once: true
delegate_to: "{{ groups['webservers'][0] }}"
Variables are the backbone of flexible Ansible automation. Whether you’re managing a simple web server deployment on a VPS or orchestrating complex multi-tier applications across dedicated servers, mastering variable usage will significantly improve your infrastructure as code practices. The key is to start simple with basic variable definitions and gradually incorporate more advanced techniques like vault encryption, complex data structures, and dynamic variable loading as your automation requirements grow.
For more detailed information about Ansible variables, check the official Ansible documentation and explore the Ansible Vault guide for security best practices.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.