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
--- - 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
--- - 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
--- - 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
--- - 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
--- - 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
--- - 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
--- - 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
--- - 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)
--- - 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
--- - 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.
--- - 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)
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
- 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.
--- - 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
# 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)
# tasks/main.yml - import_tasks: nginx.yml - import_tasks: app.yml
Include (Dynamic)
# tasks/main.yml - include_tasks: "{{ role_file }}" vars: role_file: "{{ role_name }}.yml"
When to Use
| Feature | Import | Include |
|---|---|---|
| Evaluation time | Pre-compile | Runtime |
| Tags | Applied to all tasks | Applied per task |
| Variables | Static | Dynamic |
| Performance | Faster | Slightly slower |
| Use case | Fixed structure | Dynamic structure |
Part 9: Error Handling
--- - 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
# 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
| Component | Purpose |
|---|---|
| Tasks | Individual actions using modules |
| Handlers | Triggered tasks (e.g., restart service) |
| Variables | Dynamic configuration values |
| Facts | System information gathered from hosts |
| Templates | Dynamic configuration files (Jinja2) |
| Tags | Run specific parts of playbook |
| Loops | Repeat tasks multiple times |
| Conditionals | Conditional task execution |
Learn More
Practice Ansible playbooks with hands-on exercises in our interactive labs:
https://devops.trainwithsky.com/
Comments
Post a Comment