Ansible Playbooks Cheat Sheet

Playbook Fundamentals

Basic Structure

---
- name: Playbook name
  hosts: webservers
  become: yes
  vars:
    http_port: 80
  
  tasks:
    - name: First task
      module_name:
        param1: value1
        param2: value2

Multiple Plays

---
- name: First play
  hosts: webservers
  tasks:
    - name: Task for webservers
      debug:
        msg: "This runs on webservers"

- name: Second play
  hosts: dbservers
  tasks:
    - name: Task for dbservers
      debug:
        msg: "This runs on dbservers"

Executing Playbooks

# Basic execution
$ ansible-playbook playbook.yml

# Check mode (dry run)
$ ansible-playbook --check playbook.yml

# Limit to specific hosts
$ ansible-playbook playbook.yml --limit web1.example.com

# Start at a specific task
$ ansible-playbook playbook.yml --start-at-task="Install packages"

# Step-by-step execution
$ ansible-playbook playbook.yml --step

# Show skipped hosts
$ ansible-playbook playbook.yml --list-skipped-hosts

# List tasks that would run
$ ansible-playbook playbook.yml --list-tasks

# List tags in playbook
$ ansible-playbook playbook.yml --list-tags

Variables in Playbooks

Defining Variables

# In playbook
vars:
  http_port: 80
  max_clients: 200

# From external files
vars_files:
  - vars/common.yml
  - vars/app_vars.yml

# Prompting for variables
vars_prompt:
  - name: username
    prompt: "Enter username"
    private: no
  - name: password
    prompt: "Enter password"
    private: yes

Using Variables

tasks:
  - name: Create directory
    file:
      path: "/opt/{{ app_name }}/data"
      state: directory
      
  - name: Template config
    template:
      src: "templates/{{ app_name }}.conf.j2"
      dest: "/etc/{{ app_name }}.conf"

Special Variables

# Host facts
- debug:
    msg: "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"

# Inventory variables
- debug:
    msg: "Database: {{ hostvars['db1.example.com']['ansible_host'] }}"

# Group information
- debug:
    msg: "Webservers: {{ groups['webservers'] | join(', ') }}"

Task Control

Conditionals

# Basic condition
- name: Install Apache on RedHat systems
  yum:
    name: httpd
    state: present
  when: ansible_os_family == "RedHat"

# Multiple conditions (AND)
- name: Install app if requirements met
  apt:
    name: my-app
    state: present
  when: 
    - ansible_distribution == "Ubuntu"
    - ansible_memtotal_mb > 1024

# Multiple conditions (OR)
- name: Restart if config changed
  service:
    name: httpd
    state: restarted
  when: config_file_1.changed or config_file_2.changed

# Checking variable existence
- name: Do something if variable exists
  debug:
    msg: "Variable exists"
  when: my_variable is defined

# Checking strings
- name: Check if string contains substring
  debug:
    msg: "String contains 'foo'"
  when: my_string is search('foo')

Loops

# Basic loop
- name: Create multiple users
  user:
    name: "{{ item }}"
    state: present
  loop:
    - alice
    - bob
    - charlie

# Loop with dictionary
- name: Create users with properties
  user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
    state: present
  loop:
    - { name: alice, groups: admin }
    - { name: bob, groups: developers }
    - { name: charlie, groups: developers }

# Loop with index
- name: Indexed loop example
  debug:
    msg: "{{ item.0 }} - {{ item.1 }}"
  loop: "{{ ['a', 'b', 'c'] | zip_longest(range(3), fillvalue='') | list }}"

# Nested loops
- name: Nested loop demo
  debug:
    msg: "{{ item[0] }}: {{ item[1] }}"
  loop: "{{ lookup('subelements', users, 'phones') }}"
  vars:
    users:
      - name: alice
        phones:
          - 123-456-7890
          - 234-567-8901
      - name: bob
        phones:
          - 345-678-9012

Loop Controls

# Loop with label
- name: Create users
  user:
    name: "{{ item }}"
    state: present
  loop:
    - alice
    - bob
  loop_control:
    label: "user {{ item }}"  # Cleaner output

# Pausing between loop iterations
- name: Restart servers with pause
  command: "/scripts/restart_server.sh {{ item }}"
  loop:
    - server1
    - server2
    - server3
  loop_control:
    pause: 5  # 5 second pause

# Index tracking
- name: Items with index
  debug:
    msg: "Item {{ loop_index }} is {{ item }}"
  loop:
    - apple
    - banana
    - cherry
  loop_control:
    index_var: loop_index

Handlers

Basic Handlers

tasks:
  - name: Template configuration file
    template:
      src: template.j2
      dest: /etc/foo.conf
    notify: Restart service

  - name: Some other task
    command: echo "Something else"

handlers:
  - name: Restart service
    service:
      name: foo
      state: restarted

Multiple Notifications

tasks:
  - name: Template configuration file
    template:
      src: template.j2
      dest: /etc/foo.conf
    notify:
      - Restart service
      - Log configuration change

handlers:
  - name: Restart service
    service:
      name: foo
      state: restarted
      
  - name: Log configuration change
    command: echo "Config changed" >> /var/log/config_changes.log

Handler Execution

# Force handlers to run
- name: Example playbook
  hosts: all
  tasks:
    - name: Task 1
      debug:
        msg: "Task 1"
      notify: Handler 1
      
    - name: Run handlers mid-play
      meta: flush_handlers
      
    - name: Task 2
      debug:
        msg: "Task 2"
      
  handlers:
    - name: Handler 1
      debug:
        msg: "Handler 1 executed"

Tags

Tagging Tasks

tasks:
  - name: Install packages
    apt:
      name: "{{ item }}"
      state: present
    loop:
      - nginx
      - postgresql
    tags:
      - packages
      
  - name: Configure nginx
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    tags:
      - configuration
      - nginx

Tagging Plays and Roles

# Tag entire play
- name: Webserver configuration
  hosts: webservers
  tags:
    - webservers
  tasks:
    - name: Some task
      # ...

# Tag a role
- name: Complete system setup
  hosts: all
  roles:
    - role: common
      tags: [base, common]
    - role: webserver
      tags: [web, apache]

Using Tags

# Run only tasks with specific tags
$ ansible-playbook playbook.yml --tags "configuration,packages"

# Skip tasks with specific tags
$ ansible-playbook playbook.yml --skip-tags "notification"

# Run tasks with a specific tag tree
$ ansible-playbook playbook.yml --tags "webservers"

Error Handling

Ignoring Errors

- name: This command may fail but playbook will continue
  command: /opt/might-fail.sh
  ignore_errors: yes

# Continue on skipped
- name: Run only on Ubuntu
  apt:
    name: some-package
    state: present
  when: ansible_distribution == "Ubuntu"
  any_errors_fatal: no

Block Error Handling

- block:
    - name: Update application
      command: /opt/app/update.sh
    
    - name: Restart application
      service:
        name: myapp
        state: restarted
  rescue:
    - name: Revert to previous version
      command: /opt/app/rollback.sh
    
    - name: Alert team
      mail:
        to: team@example.com
        subject: "Update failed"
        body: "Update failed on {{ inventory_hostname }}"
  always:
    - name: Always run this
      debug:
        msg: "Attempted update on {{ inventory_hostname }}"

Failing on Purpose

- name: Check if service is running
  command: systemctl status myservice
  register: service_status
  ignore_errors: yes

- name: Fail if service is not running
  fail:
    msg: "The service is not running!"
  when: service_status.rc != 0

Including and Importing

Tasks

# Static import (processed during playbook parsing)
- name: Import tasks
  import_tasks: tasks/setup.yml
  vars:
    package: nginx

# Dynamic include (processed during playbook execution)
- name: Include tasks
  include_tasks: "tasks/{{ ansible_distribution }}.yml"

Playbooks

# Import entire playbook
- name: Import playbook
  import_playbook: webserver.yml

# Conditional import
- name: Import database playbook if needed
  import_playbook: database.yml
  when: setup_database | bool

Variables with Includes

# Pass variables to included files
- name: Include with vars
  include_tasks: wordpress.yml
  vars:
    wp_version: 5.5.3
    wp_install_dir: /var/www/html

Roles

Using Roles in Playbooks

# Basic role usage
- hosts: webservers
  roles:
    - common
    - nginx
    - php-fpm

# Role with variables
- hosts: webservers
  roles:
    - role: nginx
      vars:
        http_port: 8080
        max_clients: 200

# Conditional role
- hosts: all
  roles:
    - role: monitoring
      when: enable_monitoring | bool

Including Roles in Tasks

# Include roles in tasks (dynamic)
tasks:
  - name: Run common tasks first
    debug:
      msg: "Begin setup"
      
  - name: Include nginx role
    include_role:
      name: nginx
    vars:
      http_port: 8080
      
  - name: Setup after nginx
    debug:
      msg: "Nginx setup complete"

# Import role in tasks (static)
tasks:
  - name: Import database role
    import_role:
      name: database
    vars:
      db_name: myapp

Advanced Playbook Features

Delegation

# Run task on a different host
- name: Add to load balancer
  command: /usr/bin/add-to-lb.sh {{ inventory_hostname }}
  delegate_to: lb.example.com
  
# Run locally
- name: Local task
  command: echo "Running locally"
  delegate_to: localhost
  
# Run once
- name: Send notification
  mail:
    to: admin@example.com
    subject: "Deployment started"
  run_once: true
  delegate_to: localhost

Task Control

# Serial execution (batches)
- hosts: webservers
  serial: 2  # 2 hosts at a time
  tasks:
    - name: Update application
      command: /opt/app/update.sh

# Percentage-based batches
- hosts: webservers
  serial:
    - "10%"
    - "30%"
    - "100%"

# Failure handling
- hosts: webservers
  serial: 2
  max_fail_percentage: 25  # Allow 25% failure

Async Tasks

# Fire and forget
- name: Long running operation
  command: /opt/long_operation.sh
  async: 3600  # 1 hour timeout
  poll: 0  # Don't wait

# Run with polling
- name: Update packages
  apt:
    update_cache: yes
    upgrade: dist
  async: 600  # 10 minute timeout
  poll: 15  # Check every 15 seconds

# Check status later
- name: Start backup
  command: /usr/local/bin/backup.sh
  async: 3600
  poll: 0
  register: backup_job

- name: Do other tasks
  debug:
    msg: "Doing other things"

- name: Check backup status
  async_status:
    jid: "{{ backup_job.ansible_job_id }}"
  register: job_result
  until: job_result.finished
  retries: 30
  delay: 60

Throttling

- name: Restart app servers
  command: /usr/bin/restart_app.sh
  throttle: 3  # Maximum 3 hosts in parallel

Playbook Organization

Directory Structure

project/
├── ansible.cfg
├── inventory.ini
├── playbooks/
│   ├── site.yml         # Main playbook
│   ├── webservers.yml   # Web server playbook
│   └── dbservers.yml    # Database playbook
├── roles/
│   ├── common/
│   ├── nginx/
│   └── mysql/
└── group_vars/
    ├── all.yml          # Variables for all hosts
    ├── webservers.yml   # Variables for web servers
    └── dbservers.yml    # Variables for database servers

Main Playbook (site.yml)

---
- name: Include webserver playbook
  import_playbook: webservers.yml

- name: Include database playbook
  import_playbook: dbservers.yml

Webservers Playbook

---
- name: Configure webservers
  hosts: webservers
  roles:
    - common
    - nginx
    - php-fpm

Best Practices

YAML Syntax

  • Use 2 spaces for indentation
  • Keep lines under 80 characters
  • Use consistent quoting style
  • Use > for multi-line strings
  • Use | for multi-line commands or scripts

Naming Conventions

  • Use descriptive play and task names
  • Task names should describe what they do and why
  • Start task names with verbs (Install, Configure, Start)
  • Use snake_case for variable names
  • Prefix role variables with role name (e.g., nginx_port)

Task Design

  • Make tasks idempotent (can run multiple times safely)
  • Use state parameters explicitly (present, absent, started)
  • Prefer modules over shell/command where possible
  • Use check_mode: yes for tasks that should run in check mode
  • Use changed_when/failed_when to control task status

Playbook Structure

  • Group related tasks together
  • Use includes and roles for modularity
  • Separate environment-specific variables
  • Keep playbooks focused on a specific purpose
  • Use tags for selective execution

Security

  • Use Ansible Vault for sensitive data
  • Avoid hardcoding credentials in playbooks
  • Use become only when necessary
  • Limit visibility of sensitive task output

Performance

  • Set gather_facts: no when facts aren’t needed
  • Use async for long-running tasks
  • Consider using serial for controlled deployment
  • Increase forks in ansible.cfg for faster execution

Debugging Playbooks

Debug Module

- name: Print variable value
  debug:
    var: my_variable

- name: Print custom message
  debug:
    msg: "The value is {{ my_variable }}"

# Set verbosity level
- name: Debug at level 1
  debug:
    msg: "Debug info"
    verbosity: 1  # Only with -v or higher

Verbose Output

# Increasing levels of verbosity
$ ansible-playbook playbook.yml -v
$ ansible-playbook playbook.yml -vv
$ ansible-playbook playbook.yml -vvv
$ ansible-playbook playbook.yml -vvvv

Registered Variables

- name: Run command
  command: whoami
  register: result

- name: Show command result
  debug:
    var: result

- name: Show specific properties
  debug:
    msg: "stdout: {{ result.stdout }}, rc: {{ result.rc }}"

Single-Task Execution

# Run just one task
$ ansible-playbook playbook.yml --start-at-task="Task name"

# Step through execution
$ ansible-playbook playbook.yml --step

Example Playbooks

Simple Web Server Setup

---
- name: Configure web server
  hosts: webservers
  become: yes
  vars:
    http_port: 80
    doc_root: /var/www/html
  
  tasks:
    - name: Install nginx
      apt:
        name: nginx
        state: present
        update_cache: yes
      
    - name: Create document root
      file:
        path: "{{ doc_root }}"
        state: directory
        mode: '0755'
      
    - name: Copy website files
      copy:
        src: files/website/
        dest: "{{ doc_root }}"
        mode: '0644'
      
    - name: Configure nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: Restart nginx
  
  handlers:
    - name: Restart nginx
      service:
        name: nginx
        state: restarted

Application Deployment

---
- name: Deploy application
  hosts: app_servers
  become: yes
  vars:
    app_version: "1.2.3"
    app_root: "/opt/myapp"
    app_user: "appuser"
    app_repo: "https://github.com/example/myapp.git"
  
  tasks:
    - name: Ensure app user exists
      user:
        name: "{{ app_user }}"
        state: present
        
    - name: Create app directories
      file:
        path: "{{ item }}"
        state: directory
        owner: "{{ app_user }}"
        mode: '0755'
      loop:
        - "{{ app_root }}"
        - "{{ app_root }}/releases"
        - "{{ app_root }}/shared/logs"
        - "{{ app_root }}/shared/config"
    
    - name: Clone application repository
      git:
        repo: "{{ app_repo }}"
        dest: "{{ app_root }}/releases/{{ app_version }}"
        version: "v{{ app_version }}"
      become_user: "{{ app_user }}"
    
    - name: Install dependencies
      pip:
        requirements: "{{ app_root }}/releases/{{ app_version }}/requirements.txt"
        virtualenv: "{{ app_root }}/venv"
        virtualenv_command: python3 -m venv
      become_user: "{{ app_user }}"
    
    - name: Configure application
      template:
        src: templates/app_config.j2
        dest: "{{ app_root }}/shared/config/config.yml"
        owner: "{{ app_user }}"
      notify: Restart application
    
    - name: Create symlink to current release
      file:
        src: "{{ app_root }}/releases/{{ app_version }}"
        dest: "{{ app_root }}/current"
        state: link
        owner: "{{ app_user }}"
      notify: Restart application
  
  handlers:
    - name: Restart application
      systemd:
        name: myapp
        state: restarted

Multi-Environment Deployment

---
- name: Deploy to {{ environment }} environment
  hosts: "{{ environment }}"
  become: yes
  vars_files:
    - "vars/common.yml"
    - "vars/{{ environment }}.yml"
  
  pre_tasks:
    - name: Verify environment
      fail:
        msg: "Environment must be 'development', 'staging', or 'production'"
      when: environment not in ['development', 'staging', 'production']
    
    - name: Notify start of deployment
      slack:
        token: "{{ slack_token }}"
        msg: "Starting deployment to {{ environment }}"
        channel: "#deployments"
      delegate_to: localhost
      run_once: true
      when: slack_notifications | bool
  
  roles:
    - role: common
    
    - role: database
      when: deploy_database | bool
    
    - role: application
      app_version: "{{ app_version }}"
    
    - role: monitoring
      when: environment == 'production'
  
  post_tasks:
    - name: Run smoke tests
      command: "/opt/tests/smoke_test.sh"
      register: smoke_test
      ignore_errors: "{{ environment != 'production' }}"
    
    - name: Notify deployment status
      slack:
        token: "{{ slack_token }}"
        msg: >
          Deployment to {{ environment }} 
          {% if smoke_test.rc == 0 %}succeeded{% else %}failed{% endif %}
        channel: "#deployments"
      delegate_to: localhost
      run_once: true
      when: slack_notifications | bool
Scroll to Top