Why Assert Before You Deploy
The ansible.builtin.assert module is your last line of defense before a playbook makes changes to production systems. It evaluates conditions and fails the play immediately if any condition is false โ before any damage is done.
Think of it as a pre-flight checklist: verify the target host meets all requirements before applying configuration changes.
Basic Syntax
- name: Ensure minimum Ansible version
ansible.builtin.assert:
that:
- "ansible_version.full is version('2.14', '>=')"
fail_msg: "Ansible 2.14+ required. Found {{ ansible_version.full }}"
success_msg: "Ansible version {{ ansible_version.full }} meets requirements"Key parameters:
thatโ list of conditions (all must be true)fail_msgโ custom error message on failuresuccess_msgโ optional message on successquietโ suppress success output (boolean, default false)
Validate Required Variables
The most common use case โ ensure variables are defined and non-empty before using them:
- name: Validate deployment variables
ansible.builtin.assert:
that:
- app_version is defined
- app_version | length > 0
- deploy_env is defined
- deploy_env in ['staging', 'production']
- db_host is defined
- db_port | int > 0
fail_msg: >
Missing or invalid deployment variables.
app_version={{ app_version | default('UNDEFINED') }},
deploy_env={{ deploy_env | default('UNDEFINED') }},
db_host={{ db_host | default('UNDEFINED') }}Validate Host Facts
Check that the target system meets hardware and OS requirements:
- name: Gather facts
ansible.builtin.setup:
- name: Validate target system
ansible.builtin.assert:
that:
- ansible_os_family == "RedHat"
- ansible_distribution_major_version | int >= 9
- ansible_memtotal_mb >= 4096
- ansible_processor_vcpus >= 2
- ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first | int > 5368709120
fail_msg: >
Host does not meet requirements:
OS={{ ansible_distribution }} {{ ansible_distribution_version }},
RAM={{ ansible_memtotal_mb }}MB (need 4096MB),
CPUs={{ ansible_processor_vcpus }} (need 2),
Root disk free={{ (ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first | int / 1073741824) | round(1) }}GB (need 5GB)Validate Network Connectivity
Check that required services are reachable before configuring clients:
- name: Check database connectivity
ansible.builtin.wait_for:
host: "{{ db_host }}"
port: "{{ db_port }}"
timeout: 5
register: db_check
ignore_errors: true
- name: Assert database is reachable
ansible.builtin.assert:
that:
- db_check is success
fail_msg: "Cannot reach database at {{ db_host }}:{{ db_port }}"
- name: Check API endpoint
ansible.builtin.uri:
url: "https://{{ api_host }}/healthz"
method: GET
status_code: 200
timeout: 10
register: api_check
ignore_errors: true
- name: Assert API is healthy
ansible.builtin.assert:
that:
- api_check.status == 200
fail_msg: "API health check failed: {{ api_check.msg | default('unreachable') }}"Validate Kubernetes Prerequisites
Before deploying to Kubernetes, verify the cluster is ready:
- name: Get cluster info
kubernetes.core.k8s_cluster_info:
register: cluster_info
- name: Assert Kubernetes version
ansible.builtin.assert:
that:
- cluster_info.version.server.kubernetes.major | int >= 1
- cluster_info.version.server.kubernetes.minor | int >= 28
fail_msg: "Kubernetes 1.28+ required. Found {{ cluster_info.version.server.kubernetes.gitVersion }}"
- name: Check namespace exists
kubernetes.core.k8s_info:
api_version: v1
kind: Namespace
name: "{{ deploy_namespace }}"
register: ns_check
- name: Assert namespace exists
ansible.builtin.assert:
that:
- ns_check.resources | length > 0
fail_msg: "Namespace {{ deploy_namespace }} does not exist"Validate File and Directory State
- name: Check SSL certificate exists
ansible.builtin.stat:
path: /etc/ssl/certs/app.crt
register: cert_file
- name: Assert certificate is present and not expired soon
ansible.builtin.assert:
that:
- cert_file.stat.exists
- cert_file.stat.size > 0
fail_msg: "SSL certificate missing at /etc/ssl/certs/app.crt"
- name: Get certificate expiry
ansible.builtin.command: >
openssl x509 -enddate -noout -in /etc/ssl/certs/app.crt
register: cert_expiry
changed_when: false
- name: Assert certificate not expiring within 30 days
ansible.builtin.assert:
that:
- "'notAfter' in cert_expiry.stdout"
fail_msg: "Certificate expiry check failed: {{ cert_expiry.stdout }}"Using Assert in Roles
Create a validate.yml task file that runs before the main role tasks:
# roles/webserver/tasks/validate.yml
- name: Validate webserver role variables
ansible.builtin.assert:
that:
- webserver_port | int > 0
- webserver_port | int < 65536
- webserver_document_root is defined
- webserver_ssl_enabled is defined
- webserver_ssl_enabled | bool or not webserver_force_https | default(false) | bool
fail_msg: "Invalid webserver configuration. Check role variables."
quiet: true# roles/webserver/tasks/main.yml
- name: Run validation
ansible.builtin.include_tasks: validate.yml
- name: Install web server
ansible.builtin.dnf:
name: nginx
state: present
# ... rest of roleAssert with Loops
Validate multiple items:
- name: Validate all required services are running
ansible.builtin.assert:
that:
- "item in ansible_facts.services"
- "ansible_facts.services[item].state == 'running'"
fail_msg: "Service {{ item }} is not running"
quiet: true
loop:
- sshd.service
- firewalld.service
- chronyd.serviceAssert vs Fail + When
These are equivalent:
# Using assert
- ansible.builtin.assert:
that:
- disk_free_gb | int > 10
fail_msg: "Not enough disk space"
# Using fail + when
- ansible.builtin.fail:
msg: "Not enough disk space"
when: disk_free_gb | int <= 10Use assert when you have multiple conditions to check โ it is cleaner than nested when clauses. Use fail + when for simple single-condition checks.
Production Validation Playbook Template
---
- name: Pre-deployment validation
hosts: "{{ target_hosts }}"
gather_facts: true
tasks:
- name: OS validation
ansible.builtin.assert:
that:
- ansible_os_family in ['RedHat', 'Debian']
- ansible_distribution_major_version | int >= 9 or ansible_distribution == 'Ubuntu'
fail_msg: "Unsupported OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- name: Resource validation
ansible.builtin.assert:
that:
- ansible_memtotal_mb >= min_memory_mb | default(2048)
- ansible_processor_vcpus >= min_cpus | default(2)
fail_msg: "Insufficient resources: {{ ansible_memtotal_mb }}MB RAM, {{ ansible_processor_vcpus }} CPUs"
- name: Variable validation
ansible.builtin.assert:
that:
- app_version is defined and app_version | length > 0
- deploy_env is defined and deploy_env in allowed_environments
fail_msg: "Invalid deployment variables"
- name: Connectivity validation
ansible.builtin.wait_for:
host: "{{ item.host }}"
port: "{{ item.port }}"
timeout: 5
loop: "{{ required_endpoints }}"
loop_control:
label: "{{ item.host }}:{{ item.port }}"
post_tasks:
- name: All validations passed
ansible.builtin.debug:
msg: "Pre-deployment validation complete. Safe to proceed."Building reliable automation pipelines? I help teams design Ansible automation with proper validation, testing, and production safeguards.

