Introduction

Extra variables (--extra-vars or -e) pass variables to Ansible from the command line. They have the highest precedence — overriding all other variable definitions including role defaults, inventory vars, and play vars. Perfect for one-off runs, CI/CD pipelines, and environment-specific deployments.

Basic Usage

# Single variable
ansible-playbook deploy.yml -e "app_version=2.1.0"

# Multiple variables
ansible-playbook deploy.yml -e "app_version=2.1.0 env=production"

# Long form
ansible-playbook deploy.yml --extra-vars "app_version=2.1.0"

Variable Formats

Key=Value (Simple)

ansible-playbook site.yml -e "target_host=web-01 app_port=8080"

JSON Format

# Inline JSON
ansible-playbook site.yml -e '{"app_version": "2.1.0", "debug": true}'

# Complex structures
ansible-playbook site.yml -e '{"users": ["alice", "bob"], "config": {"port": 8080, "workers": 4}}'

YAML Format

ansible-playbook site.yml -e '{app_version: 2.1.0, debug: true}'

From File (@)

# JSON file
ansible-playbook site.yml -e @vars/production.json

# YAML file
ansible-playbook site.yml -e @vars/production.yml

# Multiple files
ansible-playbook site.yml -e @vars/common.yml -e @vars/production.yml

vars/production.yml:

env: production
app_version: "2.1.0"
db_host: prod-db.example.com
replicas: 3
debug: false

vars/production.json:

{
  "env": "production",
  "app_version": "2.1.0",
  "db_host": "prod-db.example.com"
}

Variable Precedence

Extra vars have the highest precedence (position 22 of 22):

1.  command line values (not variables)
2.  role defaults
3.  inventory file/script group vars
4.  inventory group_vars/all
5.  inventory group_vars/*
6.  inventory file/script host vars
7.  inventory host_vars/*
8.  host facts / cached set_facts
9.  play vars
10. play vars_prompt
11. play vars_files
12. role vars
13. block vars
14. task vars
15. include_vars
16. set_facts / registered vars
17. role params
18. include params
19. extra vars (HIGHEST) ← --extra-vars

This means -e always wins:

# playbook.yml
- hosts: all
  vars:
    app_port: 3000    # This gets overridden
  tasks:
    - debug:
        msg: "Port: {{ app_port }}"
# Extra var wins
ansible-playbook playbook.yml -e "app_port=8080"
# Output: "Port: 8080"

Practical Patterns

CI/CD Pipeline

# GitLab CI / GitHub Actions
ansible-playbook deploy.yml \
  -e "app_version=${CI_COMMIT_TAG}" \
  -e "env=${CI_ENVIRONMENT_NAME}" \
  -e "deployed_by=${GITLAB_USER_LOGIN}" \
  -e "commit_sha=${CI_COMMIT_SHA}"

Environment Selection

# Development
ansible-playbook site.yml -e @vars/dev.yml

# Staging
ansible-playbook site.yml -e @vars/staging.yml

# Production (with override)
ansible-playbook site.yml -e @vars/prod.yml -e "replicas=5"

Target Specific Host

# Override target host
ansible-playbook deploy.yml -e "target_host=web-03"
# playbook.yml
- hosts: "{{ target_host | default('webservers') }}"
  tasks: ...

Dynamic Values

# Current timestamp
ansible-playbook deploy.yml -e "deploy_time=$(date +%Y%m%d-%H%M%S)"

# Git info
ansible-playbook deploy.yml -e "git_branch=$(git rev-parse --abbrev-ref HEAD) git_sha=$(git rev-parse --short HEAD)"

# Read from file
ansible-playbook deploy.yml -e "api_key=$(cat secrets/api_key.txt)"

Boolean and Numeric Values

# Booleans — use JSON format for correct typing
ansible-playbook site.yml -e '{"debug": true, "force_deploy": false}'

# Or key=value (treated as strings — may need | bool filter)
ansible-playbook site.yml -e "debug=true"
# In playbook — handle both formats
- debug:
    msg: "Debug mode"
  when: debug | default(false) | bool

Lists and Dictionaries

# Lists
ansible-playbook site.yml -e '{"packages": ["nginx", "redis", "git"]}'

# Dictionaries
ansible-playbook site.yml -e '{"database": {"host": "db.local", "port": 5432, "name": "myapp"}}'

Require Extra Variables

# Fail if variable not provided
- name: Validate required variables
  ansible.builtin.assert:
    that:
      - app_version is defined
      - env is defined
    fail_msg: "Required: -e 'app_version=X.Y.Z env=production'"

# Or use vars_prompt as fallback
- hosts: all
  vars_prompt:
    - name: app_version
      prompt: "App version to deploy"
      private: false

Multiple -e Flags

Later -e flags override earlier ones:

# base.yml sets debug=false
# But inline override sets debug=true (wins)
ansible-playbook site.yml -e @vars/base.yml -e "debug=true"

With Ansible Ad-Hoc

# Ad-hoc with extra vars
ansible all -m debug -a "msg={{ greeting }}" -e "greeting='Hello World'"

# Install specific version
ansible webservers -m apt -a "name=nginx={{ nginx_version }}" -e "nginx_version=1.24.0" --become

Security Considerations

# ⚠️ Passwords on command line are visible in process list
# BAD
ansible-playbook site.yml -e "db_password=secret123"

# BETTER — use file
ansible-playbook site.yml -e @secrets/db.yml

# BEST — use Ansible Vault
ansible-playbook site.yml -e @secrets/db.vault.yml --ask-vault-pass

Troubleshooting

Variable Not Being Used

# Check precedence — debug all variables
ansible-playbook site.yml -e "app_port=8080" -v
# Look for "app_port" in output

# Verify variable value
ansible all -m debug -a "var=app_port" -e "app_port=8080"

JSON Parse Error

# ❌ Wrong — single quotes inside JSON on some shells
ansible-playbook site.yml -e "{'key': 'value'}"

# ✅ Correct — use proper JSON with double quotes
ansible-playbook site.yml -e '{"key": "value"}'

# ✅ Or use a file
ansible-playbook site.yml -e @vars.json

Conclusion

Extra vars (-e) are the highest-precedence variables in Ansible — they override everything. Use them for: CI/CD pipeline values, environment selection (-e @vars/prod.yml), one-off overrides, and dynamic values from shell commands. Use @file syntax for complex structures and secrets. Always use assert to validate required extra vars in playbooks. Avoid passing secrets directly on the command line — use vault-encrypted files instead.