Introduction
The ternary filter is Ansible's inline if-else — it returns one value when a condition is true and another when false. It's cleaner than writing full {% if %} blocks when you just need to pick between two values.
{{ condition | ternary('value_if_true', 'value_if_false') }}
Basic Syntax
# Two arguments: true_value, false_value
{{ my_bool | ternary('yes', 'no') }}
# Three arguments: true_value, false_value, null_value
{{ my_var | ternary('defined_true', 'defined_false', 'undefined') }}
Simple Examples
---
- name: Ternary filter examples
hosts: localhost
gather_facts: true
vars:
debug_mode: true
env: production
max_workers: 0
app_port: 8080
tasks:
- name: Set log level based on debug flag
ansible.builtin.debug:
msg: "Log level: {{ debug_mode | ternary('DEBUG', 'INFO') }}"
# Output: "Log level: DEBUG"
- name: Choose config based on environment
ansible.builtin.debug:
msg: "SSL: {{ (env == 'production') | ternary('enabled', 'disabled') }}"
# Output: "SSL: enabled"
- name: Default workers based on value
ansible.builtin.debug:
msg: "Workers: {{ (max_workers > 0) | ternary(max_workers, ansible_processor_vcpus) }}"
- name: Set firewall rule
ansible.builtin.debug:
msg: "Port {{ app_port }} {{ (app_port == 80) | ternary('is standard HTTP', 'is non-standard') }}"
Ternary in Templates
---
- name: Generate config from template
hosts: webservers
vars:
ssl_enabled: true
workers: 4
env: production
tasks:
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
nginx.conf.j2:
server {
listen {{ ssl_enabled | ternary('443 ssl', '80') }};
server_name {{ inventory_hostname }};
{{ ssl_enabled | ternary('ssl_certificate /etc/ssl/cert.pem;', '# SSL disabled') }}
{{ ssl_enabled | ternary('ssl_certificate_key /etc/ssl/key.pem;', '') }}
worker_connections {{ (env == 'production') | ternary(1024, 256) }};
access_log {{ (env == 'production') | ternary('/var/log/nginx/access.log combined', 'off') }};
}
Ternary with Variables
---
- name: Dynamic variable assignment
hosts: all
vars:
use_custom_port: false
custom_port: 9090
default_port: 8080
tasks:
- name: Set application port
ansible.builtin.set_fact:
app_port: "{{ use_custom_port | ternary(custom_port, default_port) }}"
- name: Configure based on OS family
ansible.builtin.set_fact:
pkg_manager: "{{ (ansible_os_family == 'RedHat') | ternary('dnf', 'apt') }}"
service_name: "{{ (ansible_os_family == 'RedHat') | ternary('httpd', 'apache2') }}"
config_path: "{{ (ansible_os_family == 'RedHat') | ternary('/etc/httpd', '/etc/apache2') }}"
Three-Argument Ternary (Null Handling)
The third argument handles None/undefined values:
---
- name: Handle undefined variables
hosts: localhost
vars:
defined_true: true
defined_false: false
# undefined_var is not defined
tasks:
- name: Two args — true
ansible.builtin.debug:
msg: "{{ defined_true | ternary('YES', 'NO') }}"
# Output: "YES"
- name: Two args — false
ansible.builtin.debug:
msg: "{{ defined_false | ternary('YES', 'NO') }}"
# Output: "NO"
- name: Three args — null/undefined
ansible.builtin.debug:
msg: "{{ undefined_var | default(None) | ternary('TRUE', 'FALSE', 'UNKNOWN') }}"
# Output: "UNKNOWN"
Nested Ternary
Chain ternary filters for multiple conditions (use sparingly — readability drops fast):
- name: Nested ternary (like elif)
ansible.builtin.set_fact:
priority: >-
{{ (severity == 'critical') | ternary('P1',
(severity == 'high') | ternary('P2',
(severity == 'medium') | ternary('P3', 'P4'))) }}
For more than 2-3 conditions, use a dictionary lookup instead:
# Cleaner alternative to nested ternary
- name: Dictionary lookup (better for 3+ conditions)
ansible.builtin.set_fact:
priority: "{{ {'critical': 'P1', 'high': 'P2', 'medium': 'P3'}.get(severity, 'P4') }}"
Common Use Cases
Package Installation
- name: Install package for OS family
ansible.builtin.package:
name: "{{ (ansible_os_family == 'RedHat') | ternary('httpd', 'apache2') }}"
state: present
Conditional Service State
- name: Enable service only in production
ansible.builtin.service:
name: monitoring-agent
enabled: "{{ (env == 'production') | ternary(true, false) }}"
state: "{{ (env == 'production') | ternary('started', 'stopped') }}"
Dynamic Paths
- name: Use versioned or latest path
ansible.builtin.set_fact:
app_path: "{{ (pin_version is defined) | ternary('/opt/app-' + (pin_version | default('')), '/opt/app-latest') }}"
Boolean to String
# Convert bool to yes/no for config files
- name: Generate config line
ansible.builtin.lineinfile:
path: /etc/myapp.conf
regexp: "^debug="
line: "debug={{ debug_mode | ternary('yes', 'no') }}"
Ternary vs Jinja2 if-else
# Ternary — concise, one-liners
msg: "{{ is_prod | ternary('WARN', 'DEBUG') }}"
# Jinja2 inline if — also works, slightly different syntax
msg: "{{ 'WARN' if is_prod else 'DEBUG' }}"
# Jinja2 block — for complex logic
msg: >-
{% if env == 'prod' %}WARN{% elif env == 'staging' %}INFO{% else %}DEBUG{% endif %}
| Approach | Best For |
|---|---|
ternary | Simple true/false with clear values |
Jinja2 inline if | When the condition reads more naturally |
Jinja2 {% if %} block | Complex multi-branch logic |
Troubleshooting
"ternary expects 2 or 3 arguments"
You passed the wrong number of arguments:
# Wrong — 1 argument
{{ flag | ternary('yes') }}
# Right — 2 or 3 arguments
{{ flag | ternary('yes', 'no') }}
{{ flag | ternary('yes', 'no', 'unknown') }}
Unexpected Result with Strings
Non-empty strings are truthy in Jinja2:
# "false" (string) is TRUTHY — returns 'yes'!
{{ "false" | ternary('yes', 'no') }} # "yes"
# Convert to bool first
{{ "false" | bool | ternary('yes', 'no') }} # "no"
Integer 0 is Falsy
{{ 0 | ternary('nonzero', 'zero') }} # "zero"
{{ 1 | ternary('nonzero', 'zero') }} # "nonzero"
Related Articles
- Ansible Jinja2 Filters Guide
- Ansible when Conditional
- Ansible default Filter
- Ansible assert Module
- Ansible Ternary Operator in Jinja2 Templates
Conclusion
The ternary filter is a clean one-liner for picking between two values based on a condition. Use it for config toggles, OS-specific values, and boolean-to-string conversions. For None/undefined handling, use the three-argument form. Avoid nesting more than two deep — switch to a dictionary lookup or {% if %} block instead. Remember: non-empty strings are truthy, and "false" needs | bool before ternary.