{"id":119361,"date":"2026-06-30T10:25:58","date_gmt":"2026-06-30T04:55:58","guid":{"rendered":"https:\/\/www.guvi.in\/blog\/?p=119361"},"modified":"2026-06-30T10:26:18","modified_gmt":"2026-06-30T04:56:18","slug":"ansible-playbooks-tutorial","status":"publish","type":"post","link":"https:\/\/www.guvi.in\/blog\/ansible-playbooks-tutorial\/","title":{"rendered":"Ansible Playbooks Tutorial: Step-By-Step Guide"},"content":{"rendered":"\n<p>Managing multiple servers manually often feels repetitive and time-consuming. It usually means logging in to each system and repeatedly running the same commands. That\u2019s where the Ansible Playbooks Tutorial comes in, showing how those repeated steps can be replaced with simple, structured automation.<\/p>\n\n\n\n<p>Once that shift happens, server work starts feeling less like manual effort and more like controlled execution.&nbsp;<\/p>\n\n\n\n<p>Instead of handling everything one by one, you define the process once and let it run across systems. It naturally raises a thought\u2014how much simpler could infrastructure management be if repetition were removed completely?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>TL;DR Summary<\/strong><\/h2>\n\n\n\n<ul>\n<li>Ansible Playbooks are YAML files that tell Ansible exactly what state your servers should be in.<\/li>\n\n\n\n<li>You don&#8217;t need to memorize commands \u2014 you describe the outcome, and Ansible figures out how to get there.<\/li>\n\n\n\n<li>A working playbook needs three things: an inventory, a play, and at least one task.<\/li>\n\n\n\n<li>Most beginner mistakes come from bad YAML indentation, not bad logic.<\/li>\n\n\n\n<li>By the end of this guide, you&#8217;ll have written and run a real playbook to install and configure a web server.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<div style=\"background-color: #099f4e; border: 3px solid #110053; border-radius: 12px; padding: 18px 22px; color: #FFFFFF; font-size: 18px; font-family: Montserrat, Helvetica, sans-serif; line-height: 1.6; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); max-width: 750px;\">\n\n<strong style=\"font-size: 22px; color: #ffffff;\">\ud83d\udca1 Did You Know?<\/strong> <br \/><br \/>\n\n  <span>\n\n\n<strong style=\"color: #110053;\">Michael DeHaan<\/strong> created <strong style=\"color: #110053;\">Ansible Playbooks<\/strong> in <strong style=\"color: #110053;\">2012<\/strong> to simplify IT automation using YAML.\n\n  <\/span>\n\n<\/div>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What Is an Ansible Playbook?<\/strong><\/h2>\n\n\n\n<p>An <a href=\"https:\/\/www.redhat.com\/en\/topics\/automation\/what-is-an-ansible-playbook\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Ansible Playbook<\/a> is a YAML file containing one or more &#8220;plays,&#8221; each targeting a group of hosts and running a list of &#8220;tasks&#8221; against them.&nbsp;<\/p>\n\n\n\n<p>Playbooks describe the <em>desired end state<\/em> of a system \u2014 not the individual commands to get there. This is what makes Ansible declarative rather than procedural, and it&#8217;s the core reason teams adopt it over raw bash scripting.<\/p>\n\n\n\n<p>A playbook is made up of three nested layers:<\/p>\n\n\n\n<ul>\n<li><strong>Play<\/strong> \u2014 defines which hosts to target and what to do to them<\/li>\n\n\n\n<li><strong>Tasks<\/strong> \u2014 the individual actions within a play (install a package, copy a file, start a service)<\/li>\n\n\n\n<li><strong>Modules<\/strong> \u2014 the actual units of work Ansible runs (apt, copy, service, template, and 3,000+ others)<\/li>\n<\/ul>\n\n\n\n<p>\ud83d\udca1 <strong>In simple terms:<\/strong> Think of a playbook as a recipe, the play as &#8220;what we&#8217;re cooking,&#8221; and tasks as the individual steps in that recipe.<\/p>\n\n\n\n<p><strong>Also Read: <\/strong><a href=\"https:\/\/www.guvi.in\/blog\/what-is-ansible-in-devops\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong><em>What is Ansible in DevOps?<\/em><\/strong><\/a><\/p>\n\n\n\n<p><em>Step into automation mode. <\/em><strong><em>HCL GUVI\u2019s <\/em><\/strong><a href=\"https:\/\/www.guvi.in\/courses\/software-testing-and-automation\/ansible\/?utm_source=blog&amp;utm_medium=hyperlink&amp;utm_campaign=ansible-playbooks-tutorial\" target=\"_blank\" rel=\"noreferrer noopener\"><strong><em>Ansible Course<\/em><\/strong><\/a><em> takes you from basics to pro\u2014covering architecture, inventories, playbooks, Roles &amp; Collections, and secure automation. Build strong DevOps skills, simplify complex systems, and stay ahead in the tech world. Enroll now and power up your automation journey!<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>How does Ansible work in the background?<\/strong><\/h2>\n\n\n\n<p>Ansible connects to your servers over SSH (or WinRM for Windows), pushes small Python scripts called modules onto each host, executes them, and removes them afterwards.&nbsp;<\/p>\n\n\n\n<p>No agent software runs persistently on the target machines \u2014 this agentless design is one of the main reasons Ansible adoption grew faster than Puppet or Chef in the last decade.<\/p>\n\n\n\n<p>Here&#8217;s the execution flow in plain terms:<\/p>\n\n\n\n<ol>\n<li>You run ansible-playbook site.yml<\/li>\n\n\n\n<li>Ansible reads your inventory to find target hosts<\/li>\n\n\n\n<li>It connects via SSH using your specified credentials<\/li>\n\n\n\n<li>It transfers the required module code to each host<\/li>\n\n\n\n<li>It executes tasks <strong>in order<\/strong>, host by host (or in parallel, depending on settings)<\/li>\n\n\n\n<li>It reports back what changed, what didn&#8217;t, and what failed<\/li>\n<\/ol>\n\n\n\n<p>Because each task is idempotent by design, running the same playbook twice should never break anything \u2014 Ansible checks the current state before making changes, and skips tasks that are already satisfied.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Prerequisites Before You Start<\/strong><\/h2>\n\n\n\n<p>Before writing your first playbook, you&#8217;ll need:<\/p>\n\n\n\n<ul>\n<li>A control node (your laptop or a jump server) with Ansible installed<\/li>\n\n\n\n<li>At least one target machine reachable over SSH<\/li>\n\n\n\n<li>Passwordless SSH access (via SSH keys) is set up between the two<\/li>\n\n\n\n<li>Basic familiarity with YAML syntax \u2014 specifically indentation rules<\/li>\n<\/ul>\n\n\n\n<p>\u26a0\ufe0f <strong>Warning:<\/strong> YAML is indentation-sensitive. Using tabs instead of spaces is the single most common reason beginner playbooks fail to parse.<\/p>\n\n\n\n<p>Install Ansible on a Debian\/Ubuntu control node:<\/p>\n\n\n\n<p>sudo apt update<\/p>\n\n\n\n<p>sudo apt install ansible -y<\/p>\n\n\n\n<p>ansible &#8211;version<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step-By-Step: Writing Your First Playbook<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 1: Create Your Inventory File<\/strong><\/h3>\n\n\n\n<p>The inventory tells Ansible which machines exist and how to reach them.<\/p>\n\n\n\n<p># inventory.ini<\/p>\n\n\n\n<p>[webservers]<\/p>\n\n\n\n<p>192.168.1.10<\/p>\n\n\n\n<p>192.168.1.11<\/p>\n\n\n\n<p>[webservers:vars]<\/p>\n\n\n\n<p>ansible_user=ubuntu<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 2: Test Connectivity<\/strong><\/h3>\n\n\n\n<p>ansible -i inventory.ini webservers -m ping<\/p>\n\n\n\n<p>You should see &#8220;pong&#8221;: &#8220;pong&#8221; from each host. If you don&#8217;t, fix SSH access before moving on \u2014 nothing downstream will work otherwise.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 3: Write a Minimal Playbook<\/strong><\/h3>\n\n\n\n<p># first_playbook.yml<\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<p>&#8211; name: Basic connectivity and update check<\/p>\n\n\n\n<p>&nbsp;&nbsp;hosts: webservers<\/p>\n\n\n\n<p>&nbsp;&nbsp;become: true<\/p>\n\n\n\n<p>&nbsp;&nbsp;tasks:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&#8211; name: Update apt cache<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;apt:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;update_cache: yes<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 4: Run It<\/strong><\/h3>\n\n\n\n<p>ansible-playbook -i inventory.ini first_playbook.yml<\/p>\n\n\n\n<p>\u2705 <strong>Best Practice:<\/strong> Always run with &#8211;check first on production hosts. This performs a &#8220;dry run&#8221; \u2014 Ansible reports what <em>would<\/em> change without actually changing anything.<\/p>\n\n\n\n<p>ansible-playbook -i inventory.ini first_playbook.yml &#8211;check<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Real-World Example: Deploying Nginx with a Playbook<\/strong><\/h2>\n\n\n\n<p>Here, a real-world test of the Ansible playbook on a 5-server setup, where Nginx was automatically installed, configured, and started in a few seconds per server, compared to several minutes required for manual setup, highlighting how automation significantly speeds up deployment and reduces effort.&nbsp;<\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<p>&#8211; name: Deploy and configure Nginx<\/p>\n\n\n\n<p>&nbsp;&nbsp;hosts: webservers<\/p>\n\n\n\n<p>&nbsp;&nbsp;become: true<\/p>\n\n\n\n<p>&nbsp;&nbsp;vars:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;nginx_port: 80<\/p>\n\n\n\n<p>&nbsp;&nbsp;tasks:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&#8211; name: Install Nginx<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;apt:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name: nginx<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state: present<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;update_cache: yes<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&#8211; name: Deploy custom index page<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;template:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;src: templates\/index.html.j2<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dest: \/var\/www\/html\/index.html<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mode: &#8216;0644&#8217;<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&#8211; name: Ensure Nginx is running and enabled<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;service:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name: nginx<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state: started<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;enabled: true<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&#8211; name: Confirm Nginx is reachable<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uri:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;url: &#8220;http:\/\/localhost:{{ nginx_port }}&#8221;<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;status_code: 200<\/p>\n\n\n\n<p>Run it the same way as before:<\/p>\n\n\n\n<p>ansible-playbook -i inventory.ini nginx_playbook.yml<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Common Mistakes Beginners Make<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Mistake<\/strong><\/td><td><strong>Why It Happens<\/strong><\/td><td><strong>Fix<\/strong><\/td><\/tr><tr><td>Mixing tabs and spaces<\/td><td>YAML requires consistent spacing<\/td><td>Configure your editor to convert tabs to 2 spaces<\/td><\/tr><tr><td>Forgetting become: true<\/td><td>Tasks needing root silently fail or error<\/td><td>Add become: true at the play or task level<\/td><\/tr><tr><td>Hardcoding IPs\/usernames<\/td><td>Makes playbooks unreusable<\/td><td>Use variables and inventory groups<\/td><\/tr><tr><td>Not using &#8211;check first<\/td><td>Risky on production<\/td><td>Always dry-run before live runs<\/td><\/tr><tr><td>Treating tasks as a script<\/td><td>Misses Ansible&#8217;s idempotency benefits<\/td><td>Use modules designed for the desired state, not raw command\/shell<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>\u26a0\ufe0f <strong>Warning:<\/strong> Avoid the shell and command modules unless there&#8217;s truly no dedicated module for the job. They break idempotency, and Ansible can&#8217;t track real state changes through them.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Ansible Playbooks vs. Ad-Hoc Commands vs. Roles<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Approach<\/strong><\/td><td><strong>Best For<\/strong><\/td><td><strong>Reusable?<\/strong><\/td><td><strong>Readable at Scale?<\/strong><\/td><\/tr><tr><td>Ad-hoc commands (ansible -m)<\/td><td>One-off quick checks<\/td><td>No<\/td><td>No<\/td><\/tr><tr><td>Playbooks<\/td><td>Repeatable multi-step automation<\/td><td>Yes<\/td><td>Yes<\/td><\/tr><tr><td>Roles<\/td><td>Large, shared, modular automation across teams<\/td><td>Highly<\/td><td>Very<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Ad hoc commands are for quick, one-time actions, such as checking disk space across a fleet. Playbooks are for anything you&#8217;ll run more than once.&nbsp;<\/p>\n\n\n\n<p>Roles are playbooks broken into reusable, shareable components \u2014 the natural next step once your playbooks start repeating the same patterns across projects.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Key Takeaways<\/strong><\/h2>\n\n\n\n<ul>\n<li>Playbooks describe the desired state in YAML \u2014 they&#8217;re declarative, not scripted<\/li>\n\n\n\n<li>Three core layers: play \u2192 tasks \u2192 modules<\/li>\n\n\n\n<li>Always test SSH connectivity and run &#8211;check before live execution<\/li>\n\n\n\n<li>Avoid shell\/command modules when a dedicated module exists<\/li>\n\n\n\n<li>Once playbooks repeat patterns, it&#8217;s time to learn Roles<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What to Do Next<\/strong><\/h2>\n\n\n\n<p>Before you close this tab:<\/p>\n\n\n\n<ol>\n<li>Set up a free-tier VM (or a local VM via Vagrant) to practice safely<\/li>\n\n\n\n<li>Write the Nginx playbook above yourself, line by line \u2014 don&#8217;t copy-paste<\/li>\n\n\n\n<li>Break something on purpose, then fix it with &#8211;check and re-runs<\/li>\n\n\n\n<li>Move on to learning Ansible Roles once you&#8217;ve written 3\u20134 playbooks comfortably<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>FAQs<\/strong><\/h2>\n\n\n<div id=\"rank-math-faq\" class=\"rank-math-block\">\n<div class=\"rank-math-list \">\n<div id=\"faq-question-1782569721547\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>What is the difference between an Ansible Playbook and an Ansible role?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>A playbook is a single YAML file of tasks. A role is a structured, reusable folder of playbooks, variables, and templates meant to be shared across projects.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782569730970\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Do I need Python installed on target servers to run Ansible Playbooks?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Yes, most Ansible modules require Python on the target host, though the control node handles most of the heavy lifting itself.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782569731955\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Can Ansible Playbooks manage Windows servers?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Yes, using WinRM instead of SSH, though module support is narrower than on Linux.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782569733337\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Is Ansible Playbook syntax the same as plain YAML?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Yes, playbooks are valid YAML, but Ansible adds specific keywords like hosts, tasks, and become that only make sense inside its execution context.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782569734328\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>How do I run only part of a playbook?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Use tags. Add tags: [setup] to specific tasks, then run ansible-playbook site.yml &#8211;tags setup.<\/p>\n\n<\/div>\n<\/div>\n<\/div>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Managing multiple servers manually often feels repetitive and time-consuming. It usually means logging in to each system and repeatedly running the same commands. That\u2019s where the Ansible Playbooks Tutorial comes in, showing how those repeated steps can be replaced with simple, structured automation. Once that shift happens, server work starts feeling less like manual effort [&hellip;]<\/p>\n","protected":false},"author":64,"featured_media":119699,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[621],"tags":[],"views":"56","authorinfo":{"name":"Abhishek Pati","url":"https:\/\/www.guvi.in\/blog\/author\/abhishek-pati\/"},"thumbnailURL":"https:\/\/www.guvi.in\/blog\/wp-content\/uploads\/2026\/06\/Ansible-Playbooks-300x116.webp","_links":{"self":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119361"}],"collection":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/users\/64"}],"replies":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/comments?post=119361"}],"version-history":[{"count":5,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119361\/revisions"}],"predecessor-version":[{"id":119702,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119361\/revisions\/119702"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/media\/119699"}],"wp:attachment":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/media?parent=119361"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/categories?post=119361"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/tags?post=119361"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}