Ansible Playbook: Write and Run Your First Playbook (Complete Guide)
By Luca Berton · Published 2024-01-01 · Category: installation
How to write and run Ansible playbooks. Complete guide to playbook structure, plays, tasks, variables, handlers, loops, conditionals, and best practices.
Ansible Playbook: Write and Run Your First Playbook (Complete Guide)
An Ansible playbook is a YAML file that defines automation tasks to run on remote hosts. Playbooks are the core of Ansible — they describe the desired state of your infrastructure in a human-readable format.
See also: Ansible Variables: Define, Use, and Override Variables (Complete Guide)
Playbook Structure
---
# A playbook contains one or more plays
- name: Configure web servers # Play 1
hosts: webservers # Target hosts
become: true # Run as root
vars: # Variables
http_port: 80
tasks: # List of tasks
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Start nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers: # Run only when notified
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
- name: Configure databases # Play 2
hosts: databases
become: true
tasks:
- name: Install PostgreSQL
ansible.builtin.apt:
name: postgresql
state: present
Run a Playbook
# Basic run
ansible-playbook playbook.yml
# Specify inventory
ansible-playbook -i inventory.ini playbook.yml
# Limit to specific hosts
ansible-playbook playbook.yml --limit webserver01
# Check mode (dry run)
ansible-playbook playbook.yml --check --diff
# Extra variables
ansible-playbook playbook.yml -e "http_port=8080"
# Verbose output
ansible-playbook playbook.yml -vvv
# Start at a specific task
ansible-playbook playbook.yml --start-at-task="Install nginx"
# Step through tasks one by one
ansible-playbook playbook.yml --step
# List tasks without running
ansible-playbook playbook.yml --list-tasks
# List hosts that would be affected
ansible-playbook playbook.yml --list-hosts
See also: Configuring Ansible for VMware: Complete Setup Guide & Playbook
Variables
- hosts: webservers
become: true
vars:
app_name: mywebapp
app_port: 8080
app_user: deploy
packages:
- nginx
- python3
- python3-pip
tasks:
- name: Install packages
ansible.builtin.apt:
name: "{{ packages }}"
state: present
- name: Create app user
ansible.builtin.user:
name: "{{ app_user }}"
shell: /bin/bash
- name: Deploy config
ansible.builtin.template:
src: app.conf.j2
dest: "/etc/{{ app_name }}/config.yml"
Variable Files
- hosts: webservers
vars_files:
- vars/common.yml
- "vars/{{ ansible_os_family }}.yml"
Register Task Output
- name: Check disk space
ansible.builtin.command: df -h /
register: disk_result
changed_when: false
- name: Show disk space
ansible.builtin.debug:
var: disk_result.stdout_lines
Conditionals
- name: Install on Debian
ansible.builtin.apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: Install on RedHat
ansible.builtin.dnf:
name: nginx
state: present
when: ansible_os_family == "RedHat"
# Multiple conditions
- name: Install only on Ubuntu 24.04+
ansible.builtin.apt:
name: nginx
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_major_version | int >= 24
See also: Creating Custom Ansible Plugins to Fetch API Data Easily
Loops
# Simple loop
- name: Create multiple users
ansible.builtin.user:
name: "{{ item }}"
state: present
loop:
- alice
- bob
- charlie
# Loop with dictionaries
- name: Create users with groups
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
loop:
- { name: alice, groups: "sudo,docker" }
- { name: bob, groups: "developers" }
# Loop with index
- name: Show index
ansible.builtin.debug:
msg: "{{ idx }}: {{ item }}"
loop: ["web", "db", "cache"]
loop_control:
index_var: idx
Handlers
- hosts: webservers
become: true
tasks:
- name: Update nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- validate nginx
- restart nginx
handlers:
- name: validate nginx
ansible.builtin.command: nginx -t
listen: "validate nginx"
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
Error Handling
- name: Try risky operation
block:
- name: Attempt deployment
ansible.builtin.command: /opt/deploy.sh
- name: Verify deployment
ansible.builtin.uri:
url: http://localhost:8080/health
status_code: 200
rescue:
- name: Rollback on failure
ansible.builtin.command: /opt/rollback.sh
- name: Alert team
ansible.builtin.debug:
msg: "Deployment failed on {{ inventory_hostname }}, rolled back"
always:
- name: Clean up temp files
ansible.builtin.file:
path: /tmp/deploy-artifacts
state: absent
Tags
- hosts: webservers
tasks:
- name: Install packages
ansible.builtin.apt:
name: nginx
tags: [install, packages]
- name: Deploy config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags: [config]
- name: Start service
ansible.builtin.service:
name: nginx
state: started
tags: [service]
# Run only tagged tasks
ansible-playbook site.yml --tags "config"
# Skip tagged tasks
ansible-playbook site.yml --skip-tags "install"
Include and Import
# Static import (parsed at playbook load time)
- name: Configure webservers
hosts: webservers
tasks:
- ansible.builtin.import_tasks: tasks/install.yml
- ansible.builtin.import_tasks: tasks/configure.yml
# Dynamic include (parsed at runtime — supports loops/when)
- name: Include OS-specific tasks
ansible.builtin.include_tasks: "tasks/{{ ansible_os_family }}.yml"
Complete Example: Web App Deployment
---
- name: Deploy web application
hosts: webservers
become: true
vars:
app_version: "2.1.0"
app_user: deploy
app_dir: /opt/webapp
pre_tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
tasks:
- name: Install dependencies
ansible.builtin.apt:
name:
- nginx
- python3
- python3-venv
state: present
- name: Create app user
ansible.builtin.user:
name: "{{ app_user }}"
system: true
create_home: false
- name: Create app directory
ansible.builtin.file:
path: "{{ app_dir }}"
state: directory
owner: "{{ app_user }}"
mode: '0755'
- name: Deploy application
ansible.builtin.unarchive:
src: "https://releases.example.com/webapp-{{ app_version }}.tar.gz"
dest: "{{ app_dir }}"
remote_src: true
owner: "{{ app_user }}"
notify: restart webapp
- name: Deploy nginx config
ansible.builtin.template:
src: templates/nginx-webapp.conf.j2
dest: /etc/nginx/sites-available/webapp.conf
notify: reload nginx
- name: Enable site
ansible.builtin.file:
src: /etc/nginx/sites-available/webapp.conf
dest: /etc/nginx/sites-enabled/webapp.conf
state: link
notify: reload nginx
handlers:
- name: restart webapp
ansible.builtin.systemd:
name: webapp
state: restarted
- name: reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
post_tasks:
- name: Verify app is responding
ansible.builtin.uri:
url: "http://localhost:8080/health"
status_code: 200
retries: 5
delay: 3
FAQ
What is an Ansible playbook?
An Ansible playbook is a YAML file containing one or more plays. Each play maps a group of hosts to tasks that define the desired state. Playbooks are the primary way to automate configuration, deployment, and orchestration with Ansible.
How do I run an Ansible playbook?
Use ansible-playbook playbook.yml. Add -i inventory.ini to specify an inventory, --check for dry run, -e "var=value" for extra variables, and -v for verbose output.
What is the difference between import_tasks and include_tasks?
import_tasks is static — parsed at load time, supports tags, but can't use loops or runtime variables. include_tasks is dynamic — parsed at runtime, supports loops and conditionals, but tags don't propagate the same way.
How do I pass variables to a playbook?
Use -e on the command line (-e "version=2.0"), define in vars: section, load from vars_files:, or set in inventory. Variables from -e have the highest precedence.
What is check mode in Ansible?
Check mode (--check) is a dry run — Ansible reports what would change without making actual changes. Add --diff to see the exact differences. Not all modules support check mode.
Conclusion
Playbooks are the heart of Ansible automation. Master the structure — plays, tasks, variables, handlers, conditionals, and loops — and you can automate any infrastructure task reliably and repeatably.
Related Articles
• Getting Started with Ansible • Ansible Inventory: Complete Guide • Ansible Roles: Organize Your PlaybooksCategory: installation