Ansible Playbooks â Automation & Configuration Management Guide
In this tutorial, you'll learn about Ansible Playbooks. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Ansible playbooks are YAML-based automation scripts that define server configuration through tasks, handlers, and roles, enabling idempotent Configuration Management across thousands of servers without agent software.
What You'll Learn
Why It Matters
Configuring servers manually does not scale. When you need to update NGINX config on 200 web servers or rotate certificates across all database hosts, manual processes guarantee mistakes. Ansible playbooks execute these tasks consistently and idempotently. DodaTech uses Ansible to manage 500+ servers across dev, staging, and production â applying OS patches, updating Durga Antivirus Pro agents, and verifying Compliance with a single command.
Real-World Use
DodaZIP's release Process triggers an Ansible playbook that deploys application artifacts to 50 web servers in rolling fashion â taking each server out of the load balancer pool, updating the application, running smoke tests, and returning it to service â with zero downtime.
flowchart TD
A[Control Node] --> B[Inventory: production]
A --> C[Playbook: site.yml]
B --> D[Web Servers]
B --> E[API Servers]
B --> F[Database Servers]
C --> G[Play 1: All hosts - Base config]
C --> H[Play 2: Web - Deploy app]
C --> I[Play 3: DB - Backup config]
G --> J[Package updates]
G --> K[Monitoring agents]
H --> L[Rolling deploy]
I --> M[PostgreSQL config]
style A fill:#EE0000,color:#fff
style C fill:#EE0000,color:#fff
Prerequisites: Basic Linux administration, SSH access to target servers, and Ansible installed on the control node.
Installation
# Install Ansible on the control node
pip install ansible
# Verify installation
ansible --version
# Expected output:
# ansible [core 2.16.0]
# config file = /etc/ansible/ansible.cfg
# configured module search path = ['/home/ansible/.ansible/plugins/modules']
# ansible python module location = /home/ansible/.local/lib/python3.11/site-packages/ansible
# python version = 3.11.4 (main, Jun 7 2026, 10:23:00) [GCC 12.3.0]
# Test connectivity
ansible all -i inventory/production.ini -m ping
Inventory Management
# inventory/production.ini
[web]
web-01 ansible_host=10.0.1.10
web-02 ansible_host=10.0.1.11
web-03 ansible_host=10.0.1.12
[api]
api-01 ansible_host=10.0.2.10
api-02 ansible_host=10.0.2.11
[database]
db-primary ansible_host=10.0.3.10
db-replica-01 ansible_host=10.0.3.11
[production:children]
web
api
database
[production:vars]
ansible_user=deploy
ansible_ssh_private_key_file=/home/ansible/.ssh/production.pem
Playbook Structure
# site.yml
- name: Apply base configuration to all servers
hosts: all
become: yes
vars:
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
ssh_port: 22
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install essential packages
apt:
name:
- htop
- curl
- wget
- git
- ufw
- fail2ban
state: present
- name: Configure NTP
template:
src: templates/ntp.conf.j2
dest: /etc/ntp.conf
notify: restart ntp
handlers:
- name: restart ntp
service:
name: ntp
state: restarted
- name: Deploy web application
hosts: web
become: yes
vars:
app_version: "2.5.0"
app_port: 3000
tasks:
- name: Create app directory
file:
path: /opt/dodatech
state: directory
owner: deploy
group: deploy
mode: 0755
- name: Copy application artifact
copy:
src: "artifacts/dodazip-v{{ app_version }}.tar.gz"
dest: "/opt/dodatech/dodazip-v{{ app_version }}.tar.gz"
notify: restart app
- name: Extract application
unarchive:
src: "/opt/dodatech/dodazip-v{{ app_version }}.tar.gz"
dest: /opt/dodatech
remote_src: yes
owner: deploy
group: deploy
- name: Configure application
template:
src: templates/app-config.j2
dest: /opt/dodatech/.env
notify: restart app
- name: Ensure app service is running
service:
name: dodazip
state: started
enabled: yes
handlers:
- name: restart app
service:
name: dodazip
state: restarted
Jinja2 Templates
# templates/ntp.conf.j2
# Managed by Ansible - do not edit manually
driftfile /var/lib/ntp/ntp.drift
statistics loopstats peerstats clockstats
{% for server in ntp_servers %}
server {{ server }} iburst
{% endfor %}
restrict -4 default kod notrap nomodify nopeer noquery
restrict -6 default kod notrap nomodify nopeer noquery
restrict 127.0.0.1
restrict ::1
# templates/app-config.j2
# DodaTech Application Configuration
PORT={{ app_port }}
NODE_ENV=production
API_URL=https://api.dodatech.com
DB_HOST={{ hostvars['db-primary']['ansible_default_ipv4']['address'] }}
LOG_LEVEL=info
APP_VERSION={{ app_version }}
Idempotency Patterns
# Idempotent shell command with creates
- name: Extract archive idempotently
unarchive:
src: app-v2.tar.gz
dest: /opt/app/
# unarchive module is natively idempotent
# Making shell commands idempotent
- name: Add GPG key (only if not present)
shell: |
apt-key list | grep -q "DodaTech" || \
curl -fsSL https://repo.dodatech.com/gpg | apt-key add -
# Using handler pattern for service restarts
- name: Update configuration
template:
src: config.j2
dest: /etc/app/config.yml
notify: restart app
# Handler runs only when config actually changes
Tags for Selective Execution
- name: Security hardening
hosts: all
become: yes
tags: security
tasks:
- name: Disable root SSH login
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
notify: restart sshd
tags: ssh
- name: Configure fail2ban
template:
src: fail2ban.local.j2
dest: /etc/fail2ban/jail.local
tags: fail2ban
- name: Install UFW
package:
name: ufw
state: present
tags: firewall
- name: Install monitoring agents
hosts: all
become: yes
tags: monitoring
tasks:
- name: Deploy Prometheus node exporter
template:
src: node_exporter.service.j2
dest: /etc/systemd/system/node_exporter.service
tags: prometheus
- name: Deploy Filebeat
template:
src: filebeat.yml.j2
dest: /etc/filebeat/filebeat.yml
tags: filebeat
# Run only security tasks
ansible-playbook site.yml --tags security
# Run all tasks except monitoring
ansible-playbook site.yml --skip-tags monitoring
Common Configuration Mistakes
Playbook fails with SSH timeout: The control node cannot reach the target host on port 22. Check security groups and use
<a href="/devops/ansible/">Ansible</a> hostname -m pingto test connectivity first.Using
command/shellbreaks idempotency: Ansible cannot detect if a shell command has already run. Usecreatesorwhenconditions, or prefer dedicated modules likeapt,copy,template.Variable precedence confusion: Ansible has 22 variable precedence levels. A variable in
group_vars/allis overridden byhost_vars/hostname, which is overridden by--extra-vars. Use<a href="/devops/ansible/">Ansible</a>-inventory --varsto debug.Handlers not running when expected: Handlers run at the end of the play, not immediately after the task. If a later task depends on the handler action, use
meta: flush_handlers.Becoming root without passwordless sudo: If the remote user needs a sudo password, add
<a href="/devops/ansible/">Ansible</a>_become_passwordto inventory or use--ask-become-pass.
Practice Questions
What is idempotency in Ansible and why is it important? Answer: Idempotency means running the same playbook multiple times produces the same result. Ansible modules check current state before applying changes, allowing safe re-runs.
How do handlers differ from regular tasks? Answer: Handlers are special tasks that run only when notified by a task change. They run once at the end of the play, preventing unnecessary service restarts.
What is the purpose of
gather_facts? Answer: Fact gathering collects system information (OS, IP, memory, disks) from target hosts into<a href="/devops/ansible/">Ansible</a>_factsfor conditional execution and template variables.How does
delegate_tochange task execution? Answer: By default tasks run on the target host.delegate_toruns a task on a different host (e.g., adding a server to a load balancer pool before deploying).
Challenge
Create an Ansible playbook that bootstraps a complete web stack on a fresh Ubuntu 24.04 server: install PostgreSQL 16 with database and user, install and configure Nginx as reverse proxy, deploy a Node.js app from Git, configure systemd for the app, set up UFW firewall (SSH, HTTP, HTTPS), install fail2ban, harden SSH (disable root login, password auth), and set up SSL with Let's Encrypt.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro