Skip to main content

Command Palette

Search for a command to run...

Ansible Part 5 - Loops, Handlers and Tags

Updated
5 min read
Ansible Part 5 - Loops, Handlers and Tags
A
DevOps engineer focused on building, automating, and securing cloud infrastructure. I work with AWS, Docker, Terraform, and CI/CD pipelines to ship scalable and reliable systems. I write about cloud services & architecture, deployment strategies, and real-world DevOps practices.

As we move ahead, playbooks start getting long. You're copying the same task block with different values. You're restarting services manually after config changes. You're running the entire playbook just to test one small task you changed. This part fixes it all.

Loops eliminate repetition. Handlers make service restarts automatic and intelligent. Tags let you run just the specific parts of a playbook .


Loops

Imagine you need to create three users on a managed host. Without loops, you'd write this:

tasks:
  - name: adding user ankit
    user:
      name: dummy1
      state: present

  - name: adding user sandeep
    user:
      name: dummy2
      state: present

  - name: adding user manish
    user:
      name: dummy3
      state: present

Three tasks, same module, same arguments, only the name changes. Now imagine doing this for 20 users.

The loop keyword solves this. You write the task once and give it a list to iterate over:

tasks:
    - name: adding multiple users
      user:
        name: "{{ item }}"
        state: present
      loop:
        - dummy1
        - dummy2
        - dummy3

item is a special keyword - on each iteration, Ansible replaces it with the current value from the list.

Loops with Multiple Value per item

The simple list works when you only need one value per iteration. But what if each user needs a name, a comment, and a UID? You use a list of dictionaries and access the fields with item.field_name:

---
  - name: Using loops in playbook
    hosts: redhat
    tasks:
      - name: creating multiple users
        user:
          name: "{{ item.user_name }}"
          comment: "{{ item.comment }}"
          uid: "{{ item.uid }}"
          state: present
        loop:
          - user_name: dummy1
            comment: USER1
            uid: 10000
          - user_name: dummy2
            comment: USER2
            uid: 20000
          - user_name: dummy3
            comment: USER3
            uid: 30000

Each item in the loop is now a dictionary. item.user_name, item.comment, item.uid pull the specific fields out for each iteration.

You can also reference loops with an external variable file


Handlers

Imagine you've 50 web-servers, you deploy a new httpd.conf with some change, for that to take effect, you need to restart the service.

But there is a catch here, if you add restart.httpd at the end of your playbook, it runs every single time, even if nothing has changed. Unnecessary restarts cause unnecessary downtime.

Actual requirement -> Restart only if conf file changed.

A handler is a task that only runs when it's explicitly triggered by another task - and only if that task reported changed. If the triggering task reports ok (nothing changed), the handler never fires.

---
  - name: installing httpd package and creating index file
    hosts: redhat
    tasks:
      - name: installing httpd package
        yum:
          name: httpd
          state: present
        notify: restarting httpd

      - name: starting http service
        service:
          name: httpd
          state: started
          enabled: true

      - name: creating index.html file
        lineinfile:
          path: /var/www/html/index.html
          line: "This is testing webserver"
          create: yes

    handlers:
      - name: restarting httpd
        service:
           name: httpd
           state: restarted

Things to notice:

  1. notify: restarting httpd - this is the trigger. It points to a handler by name. When this task reports changed, it queues that handler to run.

  2. handlers: - defined at the same level as tasks:, not inside it. The handler itself is just a regular task - a module with arguments.

That last point matters. Say you have five tasks, the third one triggers a handler, and the fifth one fails. The handler won't run. Your service won't restart. This is intentional - Ansible assumes a half-broken state isn't worth triggering restarts over.


Tags - Run Only What You Need

As playbooks grow, they've a lot of tasks. Tags let you label tasks and then run only the labeled ones.

Adding tags

---
  - name: using tags in my playbook
    hosts: redhat
    tasks:
      - name: creating group
        group:
          name: developer
          state: present
        tags: group

      - name: creating user
        user:
          name: taguser0
          state: present
        tags: user

      - name: installing httpd package
        yum:
          name: httpd
          state: present
        tags: yum

      - name: creating one empty file
        file:
          path: /tmp/file.txt
          state: touch
          owner: taguser0
          group: root
          mode: "666"
        tags: file

Running tagged tasks

# Run only the yum task
ansible-playbook tags.yml --tags yum

# Run both the yum and file tasks
ansible-playbook tags.yml --tags yum,file

# Run everything EXCEPT the yum task
ansible-playbook tags.yml --skip-tags yum

Here, adding yum tag skips the rest of the tasks.

One task can have multiple tags

- name: installing httpd package
  yum:
    name: httpd
    state: present
  tags:
    - yum
    - webserver
    - packages

Tags at play level

You can tag an entire play instead of individual tasks:

- name: webserver setup
  hosts: webservers
  tags: web
  tasks:
    - name: install httpd
      yum:
        name: httpd
        state: present

    - name: start httpd
      service:
        name: httpd
        state: started

--tags web now runs the entire play. Useful when your playbook has multiple plays.


Next we'll dive into Jinja2 Templates, something beyond static files and roles, which will tell you how to structure your Ansible project. Till then stay tuned, Keep Balling!