Introduction
debug and assert look similar — both evaluate expressions and print messages. But they serve different purposes: debug is for inspecting values during development, assert is for enforcing conditions that must be true. Using debug when you need assert means silent failures; using assert for inspection clutters playbooks with unnecessary failures.
Quick Comparison
| Feature | debug | assert |
|---|---|---|
| Purpose | Print/inspect values | Validate conditions |
| On failure | Never fails | Fails the play |
| Output | Shows msg or var | Shows success_msg or fail_msg |
| Use case | Troubleshooting, development | Pre-flight checks, validation |
| Production use | Remove or disable | Keep — they're guardrails |
debug — Inspect Values
# Print a message
- ansible.builtin.debug:
msg: "Deploying version {{ app_version }} to {{ inventory_hostname }}"
# Print a variable (structured output)
- ansible.builtin.debug:
var: ansible_facts['default_ipv4']
# Print with verbosity control
- ansible.builtin.debug:
msg: "Connection details: {{ hostvars[inventory_hostname] }}"
verbosity: 2 # Only shows with -vv or higher
# Print multiple variables
- ansible.builtin.debug:
msg:
- "Host: {{ inventory_hostname }}"
- "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Memory: {{ ansible_memtotal_mb }}MB"
- "CPUs: {{ ansible_processor_vcpus }}"
Debug Patterns
# Debug registered output
- ansible.builtin.command: systemctl is-active nginx
register: nginx_status
changed_when: false
failed_when: false
- ansible.builtin.debug:
var: nginx_status
# Shows full object: stdout, stderr, rc, changed, etc.
# Debug only when troubleshooting
- ansible.builtin.debug:
msg: "{{ lookup('env', 'PATH') }}"
when: debug_mode | default(false)
tags: debug
assert — Enforce Conditions
# Validate required variables
- name: Check required variables
ansible.builtin.assert:
that:
- db_host is defined
- db_password is defined
- app_version is defined
fail_msg: "Missing required variables! Check group_vars."
success_msg: "All required variables present."
# Validate system requirements
- name: Check minimum resources
ansible.builtin.assert:
that:
- ansible_memtotal_mb >= 4096
- ansible_processor_vcpus >= 2
- ansible_mounts | selectattr('mount', 'eq', '/') | map(attribute='size_available') | first | int > 10737418240
fail_msg: "Server does not meet minimum requirements (4GB RAM, 2 CPU, 10GB disk)"
# Validate service is running before deployment
- name: Ensure database is accessible
ansible.builtin.uri:
url: "http://{{ db_host }}:5432"
register: db_check
failed_when: false
- ansible.builtin.assert:
that: db_check.status == 200
fail_msg: "Database at {{ db_host }} is not accessible!"
Assert Patterns
# Pre-flight checks at the start of playbook
- name: Pre-flight validation
hosts: all
gather_facts: true
tasks:
- name: Validate environment
ansible.builtin.assert:
that:
- env in ['dev', 'staging', 'prod']
- ansible_distribution == 'Ubuntu'
- ansible_distribution_version is version('22.04', '>=')
fail_msg: "Unsupported environment: {{ env }} on {{ ansible_distribution }} {{ ansible_distribution_version }}"
quiet: true # Suppress per-assertion output
# Validate after deployment
- name: Post-deploy validation
ansible.builtin.uri:
url: "http://localhost:{{ app_port }}/health"
register: health
- ansible.builtin.assert:
that:
- health.status == 200
- "'healthy' in health.json.status"
fail_msg: "Deployment verification failed! Health check: {{ health.json | default('no response') }}"
Using Both Together
- name: Deploy with validation
hosts: webservers
tasks:
# assert: Pre-flight check
- ansible.builtin.assert:
that: app_version is defined
fail_msg: "app_version must be set via -e"
# debug: Show what we're about to do
- ansible.builtin.debug:
msg: "Deploying {{ app_version }} to {{ inventory_hostname }}"
- name: Deploy application
ansible.builtin.include_role:
name: deploy
# debug: Show result
- ansible.builtin.debug:
msg: "Deploy complete. Verifying..."
verbosity: 1
# assert: Post-deploy validation
- ansible.builtin.uri:
url: "http://localhost:{{ app_port }}/health"
register: health
- ansible.builtin.assert:
that: health.status == 200
fail_msg: "Health check failed after deploy!"
success_msg: "✅ {{ inventory_hostname }} healthy"
Common Mistakes
# ❌ Using debug to "validate" (doesn't fail!)
- ansible.builtin.debug:
msg: "WARNING: disk space low!"
when: disk_free_mb < 1000
# This just prints — playbook continues even if disk is dangerously low
# ✅ Use assert to actually stop execution
- ansible.builtin.assert:
that: disk_free_mb >= 1000
fail_msg: "Only {{ disk_free_mb }}MB free — need at least 1000MB"
# ❌ Using assert for inspection (noisy, wrong tool)
- ansible.builtin.assert:
that: app_port > 0
success_msg: "Port is {{ app_port }}"
# Overkill for just seeing a value
# ✅ Use debug for inspection
- ansible.builtin.debug:
msg: "Port is {{ app_port }}"
# ❌ Leaving debug tasks in production playbooks
# ✅ Use verbosity or tags to control debug output
- ansible.builtin.debug:
msg: "{{ complex_variable }}"
verbosity: 2 # Only with -vv
Related Articles
Conclusion
debug = "show me this value" (development/troubleshooting). assert = "this must be true or stop" (validation/guardrails). Use assert at the start of playbooks for pre-flight checks and after critical operations for verification. Use debug during development, then either remove it or gate it behind verbosity for production. The combination of both creates defensive playbooks that validate assumptions explicitly instead of failing mysteriously halfway through.