Ansible Playbook Development: A Step-by-step Guide

December 11, 2023 - Updated: November 2, 2026 - Words by  Lucija Korbar - 9 min read

Card image caption

Ansible Playbook development is the process of planning, writing, testing and maintaining the YAML files that tell Ansible what state your systems should be in. Writing a Playbook that runs is the quick part. Most of the work goes into making sure it does the right thing every time, on every host, no matter who wrote it.

This guide walks through that workflow with one example from start to finish. You’ll set up a development environment and write a Playbook that configures an nginx documentation server. Then you’ll check it with ansible-lint and Steampunk Spotter, fix what they find, and run it on a test system to prove it’s idempotent.

Let’s jump right in!

What is an Ansible Playbook?

An Ansible Playbook is a YAML file with one or more plays. Each play maps a group of hosts from your inventory to an ordered list of tasks, and each task calls a module, such as ansible.builtin.package or ansible.builtin.service. You run it with the ansible-playbook command. Ansible then connects to each host and changes only what doesn’t already match the state you described.

Before you start: set up your development environment

You need Python 3.10 or later on your control node. Install the Ansible development tools:

---
pip install ansible-dev-tools 
adt --version

This installs ansible-core and ansible-lint, which you’ll use later in this guide, along with tools such as ansible-navigator and Molecule. If you work in VS Code, add the Ansible extension from Red Hat. It gives you autocomplete, inline docs and lint warnings while you type.

1. Start with a plan

Before you dive in to writing, take some time to think about what your playbook needs to do. What tasks do you need to automate? What hosts or groups of hosts do you need to apply the playbook to? Once you have a good understanding of your requirements, you can start to break down the playbook into smaller, more manageable tasks.

2. Write a diagram

When you have all your answers, write up a high-level diagram of what your playbook should do. This will help you visualize all the steps involved in the playbook and identify any potential dependencies.

When making a basic diagram of an Ansible Playbook, remember it should:

  • Be easy to understand and not cluttered with too much detail.

  • Show the overall flow of the playbook, including the steps involved and any dependencies between them.

  • Identify any key decision points or branching logic.

  • Use clear and concise labels.

In our case, the diagram looks like this:

Diagram of the Ansible Playbook steps for setting up an nginx documentation server

3. Find the right collections and approved modules

After you’ve visualized your playbook, it’s time to research available modules, the building blocks of your automation. There are many different modules available, covering a wide range of tasks, and you can find them on Ansible Galaxy. But unfortunately, there’s no way of telling if the examples are actually of high-quality, meaning there’s no guarantee that they will do what they’re supposed to do, or even if they are actually safe to use in your environment.

For better assurance, use the Complete Ansible Collections Documentation generated by XLAB Steampunk team of Ansible experts. This documentation includes examples that have been checked for correctness and can be safely used in your playbooks.

4. Write the playbook

Once you have a good understanding of the tasks you need to automate and are sure that the collections and modules you’re about to include in your playbook are high-quality, you can start writing your playbook. Be sure to organize it into distinct tasks, structured plays, and well-defined variables and conditionals to ensure your playbooks are clear, maintainable and efficient.

Check out the Ultimate guide for high-quality Ansible Playbooks to get started. Or get some help from AI to speed things up even more.

In our case, the playbook looks like this:

---
- name: An example playbook
  hosts: localhost
  vars:
    documentation_user:
      username: "documento"
      home_dir: "/home/documento"
      doc_dir: "/home/documento/www-data"
  tasks:
    - name: Install prerequisite packages
      ansible.builtin.package:
        name:
          - nginx
          - python3-pip
          - rssh
        state: present

    - name: Create group
      ansible.builtin.group:
        name: "{{ documentation_user.username }}"
        state: present

    - name: Create a login user
      ansible.builtin.user:
        name: "{{ documentation_user.username }}"
        group: "{{ documentation_user.username }}"
        state: present
        shell: /usr/bin/rssh
        system: "no"
        createhome: true
        home: "{{ documentation_user.home_dir }}"

    - name: Allow deployment pipeline access as the documentation user
      ansible.posix.authorized_key:
        user: "{{ documentation_user.username }}"
        state: present
        key: "{{ lookup('file', 'keys/documentation_ecdsa.pub') }}"

    - name: Create directory for documentation files
      ansible.builtin.file:
        path: "{{ documentation_user.doc_dir }}"
        state: directory
        owner: "{{ documentation_user.username }}"
        group: "{{ documentation_user.username }}"

    - name: Install Ansible PKI prerequisites.
      ansible.builtin.pip: name=pyOpenSSL

    - name: Generate an OpenSSL private key
      openssl_privatekey:
        path: /etc/ssl/private/documentation.key
      register: openssl_private_key_result

    - name: Generate an OpenSSL Certificate Signing Request
      openssl_csr:
        path: /etc/ssl/private/documentation.csr
        privatekey_path: /etc/ssl/private/documentation.key
      register: openssl_csr_result

    - name: Generate a Self Signed OpenSSL certificate
      openssl_certificate:
        path: /etc/ssl/certs/documentation.crt
        privatekey_path: /etc/ssl/private/documentation.key
        csr_path: /etc/ssl/private/documentation.csr
        provider: selfsigned
      register: openssl_crt_result

    - name: Deploy the rssh config file
      ansible.builtin.template:
        src: rssh.conf
        dest: /etc/rssh.conf
        mode: "0644"

    - name: Deploy the nginx config file
      action:
        module: ansible.builtin.template
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        validate: "nginx -t -c %s"
        mode: "0644"
      register: nginx_conf_result

    - ansible.builtin.service:
        name: nginx
        state: restarted
      when: openssl_private_key_result.changed or openssl_csr_result.changed or openssl_crt_result.changed or nginx_conf_result.changed

5. Test your playbook

Even if you’ve mapped out your playbook, dove deep into module research, and poured your heart and soul into writing your playbook, does that guarantee it’ll work? Well, sorry to break it to you, but nope! But don’t fret, there are awesome tools out there to whip your playbooks into shape in no time.

Check it with Ansible Lint

First, always use Ansible Lint. Lint is a tool that will do a basic check of your playbook for syntax errors and general playbook writing best practices. Ansible Lint will make sure your playbook is correctly written, which is the basic requirement for your playbook to work.

Let’s lint the playbook.

ansible-lint output stopping at a missing ansible.posix collection

As we can see, Lint exited at first error, since it did not recognize the ansible.posix collection, which we have not installed. Which is okay as Lint isn’t meant to dive deep into playbook analysis but only take care of the basics. But just because the syntax is correct doesn’t mean your playbook will actually do what it’s supposed to. So, here’s another tool to the rescue.

Check it with Steampunk Spotter

Steampunk Spotter is a governance layer for Ansible and Terraform code. It checks every change against your policy before it runs, using built-in checks for security, compliance and best practices, and it can rewrite many of the issues it finds automatically.

Let’s run a Spotter scan.

Steampunk Spotter scan results for the example Ansible Playbook

As we can see, Spotter gave us valuable information to work with. Most of the issues we are dealing with can be fixed using the rewrite function. Let’s do that using spotter scan --rewrite playbook.yml command.

Spotter rewrite output showing fixed issues and remaining checks

We fixed most of the issues we were dealing with, and Spotter nicely shows precisely what was done and which checks still remain. We are left with only some hints and warnings which are quite easy to fix manually, and do not take a lot of time, but are very important if we want to follow best practices.

Spotter also generated a requirements file for us, so let’s go ahead and install it.

Installing the requirements file generated by Spotter
Installing the requirements file generated by Spotter

Let’s check the playbook with Spotter to see if we cleared up all the issues.

Spotter scan showing no remaining errors

Spotter says we’re good to go.

With the Spotter checks passed, let’s rerun Ansible Lint to confirm the playbook is clean.

ansible-lint passing on the fixed Playbook

Run it on a test system

Before production, run the fixed Playbook on a test system. Add –check –diff first to see what Ansible would change without changing anything, then run it without them:

ansible-playbook playbook.yml --become --ask-become-pass --check --diff 

Then run it a second time. A well-written Playbook is idempotent, so the PLAY RECAP should show changed=0 and failed=0. That means every task found the system already in the state you described and left it alone.

Try Steampunk Spotter

6. Deploy playbook to production

Once you have tested your playbook thoroughly in the test environment, you can deploy it to production. Putting your playbooks into action on real systems is a big deal in Ansible Playbook development. It’s crucial to watch closely and double-check to make sure everything is working right, and your system is performing its best.

ansible-playbook run output showing all documentation server tasks completed successfully

Here is the finished playbook.

---
- name: Prepare and deploy documentation server
  hosts: localhost
  vars:
    documentation_user:
      username: "documento"
      home_dir: "/home/documento"
      doc_dir: "/home/documento/www-data"
  tasks:
    - name: Install prerequisite packages
      ansible.builtin.package:
        name:
          - nginx
          - python3-pip
          - rssh
        state: present

    - name: Create group
      ansible.builtin.group:
        name: "{{ documentation_user.username }}"
        state: present

    - name: Create a login user
      ansible.builtin.user:
        name: "{{ documentation_user.username }}"
        group: "{{ documentation_user.username }}"
        state: present
        shell: /usr/bin/rssh
        system: false
        create_home: true
        home: "{{ documentation_user.home_dir }}"

    - name: Allow deployment pipeline access as the documentation user
      ansible.posix.authorized_key:
        user: "{{ documentation_user.username }}"
        state: present
        key: "{{ lookup('file', 'keys/documentation_ecdsa.pub') }}"

    - name: Create directory for documentation files
      ansible.builtin.file:
        path: "{{ documentation_user.doc_dir }}"
        state: directory
        owner: "{{ documentation_user.username }}"
        group: "{{ documentation_user.username }}"
        mode: "0777"

    - name: Install Ansible PKI prerequisites.
      ansible.builtin.pip:
        name: pyOpenSSL

    - name: Generate an OpenSSL private key
      community.crypto.openssl_privatekey:
        path: /etc/ssl/private/documentation.key
      register: openssl_private_key_result

    - name: Generate an OpenSSL Certificate Signing Request
      community.crypto.openssl_csr:
        path: /etc/ssl/private/documentation.csr
        privatekey_path: /etc/ssl/private/documentation.key
      register: openssl_csr_result

    - name: Generate a Self Signed OpenSSL certificate
      community.crypto.x509_certificate:
        path: /etc/ssl/certs/documentation.crt
        privatekey_path: /etc/ssl/private/documentation.key
        csr_path: /etc/ssl/private/documentation.csr
        provider: selfsigned
      register: openssl_crt_result

    - name: Deploy the rssh config file
      ansible.builtin.template:
        src: rssh.conf
        dest: /etc/rssh.conf
        mode: "0644"

    - name: Deploy the nginx config file
      ansible.builtin.template:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        validate: "nginx -t -c %s"
        mode: "0644"
      register: nginx_conf_result

    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted
      when: openssl_private_key_result.changed or openssl_csr_result.changed or openssl_crt_result.changed or nginx_conf_result.changed

7. Document your playbook

Once you have tested and deployed your playbook, take some time to thoroughly document it. The future you will be thankful ;)

Creating clear documentation for your Ansible Playbooks has several advantages. It helps everyone involved to better understand and use them, improving overall collaboration and efficiency, and it also makes playbooks easier to maintain and update, as it provides a clear record of what the playbook does and how it works. Plus, well-documented playbooks can be easily repurposed for different tasks, saving both time and effort. It’s a crucial step worth taking.

8. Keep your playbook in check

You’ve crafted a flawless playbook following all the steps, assuring reliable, secure, and efficient playbook execution. But remember, nothing is time-proof, especially playbooks. Regularly check if they still comply with best practices, your policies, and security standards. potter can run the same checks in your CI/CD pipeline, so every change is checked against your policies before it runs, whether an engineer or an AI assistant wrote it. Give it a try!

If you want to dive deeper into tips for writing high-quality Ansible Playbooks, give this a glance:


Frequently Asked Questions

What language are Ansible Playbooks written in?

Ansible Playbooks are written in YAML, with Jinja2 syntax for variables and templates, such as {{ documentation_user.username }}. Indentation defines the structure, so use two spaces per level and never tabs. A misplaced indent is one of the most common reasons a Playbook fails, and ansible-lint flags it before you run anything.

How do you test an Ansible Playbook before production?

Run ansible-lint for syntax and best-practice issues, then check the Playbook with Steampunk Spotter against your security and compliance policies. Spotter can rewrite many of the issues it finds automatically. After that, run the Playbook with --check --diff on a test system, then run it for real twice to confirm it’s idempotent.

How difficult is it to learn Ansible Playbook development?

Most engineers can write and run a simple Playbook within a day, because YAML is readable and modules handle the hard parts. Writing Playbooks that are secure, idempotent and easy to maintain takes longer, so it pays to add linting and policy checks to your workflow early.

Get a free, expert-led review of your Ansible Playbooks - register for assessment

Found this post useful?

Get our monthly newsletter.

Thank you for subscribing!

Please wait

Processing, please wait...

Keep up with what we do on our social media.