Skip to main content

Ansible Playbooks

 

Ansible Playbooks: The Complete Guide to Automation

📅 Published: August 2026
⏱️ Estimated Reading Time: 16 minutes
🏷️ Tags: Ansible Playbooks, Automation, YAML, DevOps, Configuration Management


Introduction: What is an Ansible Playbook?

An Ansible playbook is a YAML file that defines automation steps for your infrastructure.

Think of a playbook as a recipe. It tells Ansible what ingredients (modules) to use and in what order to apply them. Each step is a task that runs a module with specific arguments.

Why playbooks matter:

  • Human-readable: YAML is easy to read and write

  • Idempotent: Run the same playbook multiple times with the same result

  • Declarative: You describe what the final state should be, not how to get there

  • Reusable: Share playbooks across teams and projects

  • Version-controlled: Store in Git like any other code


Part 1: Playbook Structure

Basic Playbook

yaml
---
- name: Install and configure Nginx
  hosts: webservers
  become: yes
  tasks:
    - name: Install Nginx package
      apt:
        name: nginx
        state: present

    - name: Start Nginx service
      service:
        name: nginx
        state: started
        enabled: yes

Playbook Components

yaml
---
- name: Playbook Description
  hosts: inventory_group
  become: yes                # Use privilege escalation (sudo)
  remote_user: deploy        # SSH user
  vars:                      # Variables for this play
    app_port: 8080
    app_user: webapp
  pre_tasks:                 # Tasks before main roles/tasks
    - name: Update apt cache
      apt:
        update_cache: yes
  tasks:                     # Main task list
    - name: Install packages
      apt:
        name: "{{ item }}"
      loop:
        - git
        - curl
  handlers:                  # Actions triggered by notify
    - name: restart nginx
      service:
        name: nginx
        state: restarted
  post_tasks:                # Tasks after main roles/tasks
    - name: Print completion
      debug:
        msg: "Deployment complete"

Part 2: Basic Playbook Examples

Example 1: Install and Configure Nginx

yaml
---
- name: Configure Nginx web server
  hosts: webservers
  become: yes
  vars:
    nginx_port: 80
    server_name: "{{ inventory_hostname }}"
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
      when: ansible_os_family == "Debian"

    - name: Copy website files
      copy:
        src: ./website/
        dest: /var/www/html/
        owner: www-data
        group: www-data
        mode: '0644'

    - name: Configure Nginx site
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: restart nginx

    - name: Remove default site
      file:
        path: /etc/nginx/sites-enabled/default
        state: absent
      notify: restart nginx

    - name: Enable site
      file:
        src: /etc/nginx/sites-available/default
        dest: /etc/nginx/sites-enabled/default
        state: link
      notify: restart nginx

    - name: Start Nginx
      service:
        name: nginx
        state: started
        enabled: yes

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

Example 2: User Management

yaml
---
- name: Create and configure users
  hosts: all
  become: yes
  vars:
    users:
      - name: alice
        groups: developers
        shell: /bin/bash
      - name: bob
        groups: developers
        shell: /bin/bash
    authorized_keys:
      - user: alice
        key: "ssh-rsa AAAAB3..."
      - user: bob
        key: "ssh-rsa AAAAC4..."

  tasks:
    - name: Create groups
      group:
        name: "{{ item }}"
        state: present
      loop:
        - developers
        - operations

    - name: Create users
      user:
        name: "{{ item.name }}"
        groups: "{{ item.groups }}"
        shell: "{{ item.shell }}"
        state: present
        create_home: yes
      loop: "{{ users }}"

    - name: Add SSH authorized keys
      authorized_key:
        user: "{{ item.user }}"
        key: "{{ item.key }}"
        state: present
      loop: "{{ authorized_keys }}"

    - name: Set sudoers for developers
      copy:
        dest: /etc/sudoers.d/developers
        content: |
          %developers ALL=(ALL) NOPASSWD: ALL
        owner: root
        group: root
        mode: '0440'

Example 3: Deploy a Node.js Application

yaml
---
- name: Deploy Node.js application
  hosts: appservers
  become: yes
  vars:
    app_name: myapp
    app_port: 3000
    app_version: v1.2.0
    app_user: nodeapp

  tasks:
    - name: Create app user
      user:
        name: "{{ app_user }}"
        state: present
        create_home: yes

    - name: Install system dependencies
      apt:
        name:
          - nodejs
          - npm
          - git
          - pm2
        state: present

    - name: Create app directory
      file:
        path: /opt/{{ app_name }}
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_user }}"

    - name: Clone application
      git:
        repo: "https://github.com/company/{{ app_name }}.git"
        dest: /opt/{{ app_name }}
        version: "{{ app_version }}"
      become_user: "{{ app_user }}"

    - name: Install app dependencies
      npm:
        path: /opt/{{ app_name }}
        state: present
      become_user: "{{ app_user }}"

    - name: Start app with PM2
      pm2:
        name: "{{ app_name }}"
        script: /opt/{{ app_name }}/app.js
        state: started
        env:
          NODE_ENV: production
          PORT: "{{ app_port }}"
      become_user: "{{ app_user }}"

    - name: Save PM2 process list
      pm2:
        name: "{{ app_name }}"
        state: started
        save: yes

    - name: Set up PM2 startup
      shell: |
        pm2 startup -u {{ app_user }} --hp /home/{{ app_user }}
        pm2 save

Part 3: Variables and Facts

Using Variables

yaml
---
- hosts: webservers
  vars:
    app_name: myapp
    app_port: 8080
    app_env: production

  tasks:
    - name: Create config file
      template:
        src: config.j2
        dest: /etc/{{ app_name }}/config.yml

    - name: Start app
      shell: "node app.js --port {{ app_port }} --env {{ app_env }}"

Using Facts

yaml
---
- hosts: webservers
  tasks:
    - name: Debug facts
      debug:
        var: ansible_facts

    - name: Print specific facts
      debug:
        msg: |
          OS: {{ ansible_facts.os_family }}
          Distro: {{ ansible_facts.distribution }} {{ ansible_facts.distribution_version }}
          IP: {{ ansible_facts.default_ipv4.address }}
          CPU: {{ ansible_facts.processor_cores }} cores

    - name: Conditional based on OS
      apt:
        name: apache2
        state: present
      when: ansible_facts.os_family == "Debian"

    - name: Conditional based on IP
      debug:
        msg: "This is the primary server"
      when: ansible_facts.default_ipv4.address == "192.168.1.10"

Register Variables

yaml
---
- hosts: webservers
  tasks:
    - name: Run command and capture output
      command: "df -h /"
      register: disk_usage

    - name: Print command output
      debug:
        var: disk_usage.stdout

    - name: Check disk usage
      fail:
        msg: "Disk usage is too high: {{ disk_usage.stdout }}"
      when: disk_usage.stdout is search("90%")

Part 4: Conditionals and Loops

Conditionals (when)

yaml
---
- hosts: webservers
  tasks:
    # Simple condition
    - name: Install Apache on Debian
      apt:
        name: apache2
        state: present
      when: ansible_facts.os_family == "Debian"

    # Multiple conditions
    - name: Install packages
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - curl
      when: ansible_facts.os_family == "Debian"

    # Condition with variable
    - name: Start service
      service:
        name: "{{ service_name }}"
        state: started
      when: start_service == true

    # Condition on previous task result
    - name: Create user
      user:
        name: alice
        state: present
      register: user_result

    - name: Notify on user creation
      debug:
        msg: "User alice was created"
      when: user_result.changed

Loops

yaml
---
- hosts: webservers
  tasks:
    # Simple loop
    - name: Install multiple packages
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - curl
        - vim

    # Loop with dict
    - name: Create multiple users
      user:
        name: "{{ item.name }}"
        groups: "{{ item.groups }}"
        state: present
      loop:
        - { name: 'alice', groups: 'developers' }
        - { name: 'bob', groups: 'developers' }
        - { name: 'charlie', groups: 'operations' }

    # Loop with file list
    - name: Copy files
      copy:
        src: "{{ item }}"
        dest: /etc/configs/
      with_fileglob:
        - "files/*.conf"

    # Nested loops
    - name: Create users with groups
      user:
        name: "{{ item.user }}"
        groups: "{{ item.group }}"
      loop:
        - { user: alice, group: devs }
        - { user: bob, group: devs }

Part 5: Handlers and Notify

Handlers are tasks that run only when triggered by notify.

yaml
---
- hosts: webservers
  become: yes
  tasks:
    - name: Copy Nginx config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify:
        - restart nginx
        - reload firewall

    - name: Copy SSL certificates
      copy:
        src: "{{ item }}"
        dest: /etc/nginx/ssl/
      loop:
        - cert.pem
        - key.pem
      notify: restart nginx

    - name: Update Nginx config
      template:
        src: site.conf.j2
        dest: /etc/nginx/sites-available/site.conf
      notify: restart nginx

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

    - name: reload firewall
      command: firewall-cmd --reload

    - name: restart nginx
      service:
        name: nginx
        state: restarted

Part 6: Templates (Jinja2)

Template File (nginx.conf.j2)

jinja2
user www-data;
worker_processes {{ ansible_processor_cores }};

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    server {
        listen {{ nginx_port }};
        server_name {{ server_name }};

        location / {
            proxy_pass http://{{ app_host }}:{{ app_port }};
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location /static/ {
            alias /var/www/static/;
        }
    }
}

Using Templates

yaml
- name: Generate Nginx config
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  vars:
    nginx_port: 80
    server_name: "{{ inventory_hostname }}"
    app_host: "{{ groups['appservers'][0] }}"
    app_port: 8080

Part 7: Tags

Tags allow you to run specific parts of a playbook.

yaml
---
- hosts: webservers
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
      tags:
        - nginx
        - install

    - name: Configure Nginx
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      tags:
        - nginx
        - configure

    - name: Start Nginx
      service:
        name: nginx
        state: started
      tags:
        - nginx
        - start

    - name: Deploy application
      copy:
        src: app/
        dest: /opt/app/
      tags:
        - app
        - deploy
bash
# Run only tasks tagged with 'nginx'
ansible-playbook site.yml --tags nginx

# Run all tasks except 'deploy'
ansible-playbook site.yml --skip-tags deploy

# Multiple tags
ansible-playbook site.yml --tags "nginx,app"

Part 8: Import vs Include

Import (Static)

yaml
# tasks/main.yml
- import_tasks: nginx.yml
- import_tasks: app.yml

Include (Dynamic)

yaml
# tasks/main.yml
- include_tasks: "{{ role_file }}"
  vars:
    role_file: "{{ role_name }}.yml"

When to Use

FeatureImportInclude
Evaluation timePre-compileRuntime
TagsApplied to all tasksApplied per task
VariablesStaticDynamic
PerformanceFasterSlightly slower
Use caseFixed structureDynamic structure

Part 9: Error Handling

yaml
---
- hosts: webservers
  tasks:
    # Ignore errors
    - name: Risky operation
      command: /usr/local/bin/risky-script
      register: result
      ignore_errors: yes

    - name: Debug output
      debug:
        var: result

    # Fail if condition
    - name: Check success
      fail:
        msg: "Script failed with: {{ result.stderr }}"
      when: result.failed

    # Retry on failure
    - name: Retry unstable command
      command: /usr/local/bin/unstable-script
      register: retry_result
      until: retry_result.rc == 0
      retries: 5
      delay: 10

    # Continue on failure
    - name: Optional task
      command: /usr/local/bin/optional-script
      failed_when: false

    # Error handling block
    - block:
        - name: Try this
          command: /usr/local/bin/risky-script

        - name: And this
          command: /usr/local/bin/another-script

      rescue:
        - name: This runs on failure
          debug:
            msg: "Something went wrong!"

      always:
        - name: This always runs
          debug:
            msg: "Cleaning up..."

Playbook Commands Cheat Sheet

bash
# Run playbook
ansible-playbook site.yml

# Syntax check
ansible-playbook site.yml --syntax-check

# Verbose output
ansible-playbook site.yml -v
ansible-playbook site.yml -vvvv

# Dry run (check mode)
ansible-playbook site.yml --check

# Show file changes (diff mode)
ansible-playbook site.yml --diff

# Run with tags
ansible-playbook site.yml --tags nginx

# Start at a specific task
ansible-playbook site.yml --start-at-task "Install Nginx"

# Limit to specific hosts
ansible-playbook site.yml -l webserver1

# Ask vault password
ansible-playbook site.yml --ask-vault-pass

Summary

ComponentPurpose
TasksIndividual actions using modules
HandlersTriggered tasks (e.g., restart service)
VariablesDynamic configuration values
FactsSystem information gathered from hosts
TemplatesDynamic configuration files (Jinja2)
TagsRun specific parts of playbook
LoopsRepeat tasks multiple times
ConditionalsConditional task execution

Learn More

Practice Ansible playbooks with hands-on exercises in our interactive labs:
https://devops.trainwithsky.com/

Comments

Popular posts from this blog

🌐 Holographic Communications & 6G: The Future of Immersive Connectivity

  🌐 Holographic Communications & 6G: The Future of Immersive Connectivity 🚀 Introduction As the world moves towards 6G , a revolutionary technology is set to redefine digital interactions: Holographic Communications . Imagine real-time, 3D holographic video calls, immersive remote collaboration, and lifelike virtual experiences —all powered by ultra-fast, ultra-low-latency 6G networks . This topic explores Holographic Communications , its impact on various industries, key enabling technologies, and how 6G will bring this futuristic concept to reality . Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🔍 1. What is Holographic Communication? Hologr...

How to Use SKY TTS: The Complete, Step-by-Step Guide for 2025

 What is SKY TTS? SKY TTS  is a free, next-generation  AI audio creation platform  that brings together high-quality  Text-to-Speech ,  Speech-to-Text , and a full suite of professional  audio editing tools  in one seamless experience. Our vision is simple — to make advanced audio technology  free, accessible, and effortless  for everyone. From creators and educators to podcasters, developers, and businesses, SKY TTS helps users produce  studio-grade voice content  without expensive software or technical skills. With support for  70+ languages, natural voices, audio enhancement, waveform generation, and batch automation , SKY TTS has become a trusted all-in-one toolkit for modern digital audio workflows. Why Choose SKY TTS? Instant Conversion:  Enjoy rapid text-to-speech generation, even with large documents. Advanced Voice Settings:   Adjust speed, pitch, and style for a personalized listening experience. Multi-...

📊 Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd

  Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd Monitoring and logging are essential for maintaining a healthy and well-performing Kubernetes cluster. In this guide, we’ll cover why monitoring is important, key monitoring tools like Prometheus and Grafana, and logging tools like Fluentd to help you gain visibility into your cluster’s performance and logs. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction In today’s fast-paced cloud-native environment, Kubernetes has emerged as the de-facto container orchestration platform. But deploying and managing applications in Kubernetes is just half the ba...