Elevating IaC Workflows with Spacelift Stacks and Dependencies 🛠️

Register for the July 23 demo →

Ansible

Ansible Tags Explained – Examples & Best Practices

ansible tags

Ansible is an open-source tool primarily used for infrastructure automation. It allows you to simplify repetitive tasks, application deployments, configurations, orchestration, security and compliance tasks, and cloud provisioning using YAML-based playbooks.

An Ansible best practice is to use tags to manage your playbooks. In this article, we show you the benefits of leveraging Ansible tags and how to use them for different scenarios.

What we will cover:

  1. What are Ansible tags?
  2. Benefits of using Ansible tags
  3. How to use tags in Ansible?
  4. How to list all tags in Ansible?
  5. Using multiple tags in Ansible
  6. How to skip tags in playbooks?
  7. ‘Always’ and ‘Never’ tags 
  8. Advanced usage of tags
  9. Best practices for using tags in Ansible
  10. Ansible tags in real-world examples

What are Ansible tags?

Ansible tags provide users with granular control over the execution of specific tasks, roles, and even entire plays within a playbook. Tags are specified in the Playbook YAML file and assigned to a task or role. When using the –tags parameter in the ansible-playbook command, Ansible will execute the tagged tasks only and ignore the rest. This feature is particularly useful when you want to avoid running the entire playbook and focus instead on specific components to prevent potential disruptions in your environment. 

You might be wondering — why you can’t just use conditionals to control your playbook tasks or roles. Conditionals are based on certain conditions being true for that task or role to run, and tags give you more control by allowing you to run that task or role directly from the command line. 

Benefits of using Ansible tags

Ansible tags offer many benefits and may provide huge assistance in particular situations.

With tags, you can easily debug your Ansible playbook when something isn’t working properly. Placing a tag on the task you want to test and running the tagged items via the command line can help you get to the root cause without running the entire playbook. Playbooks can easily become complex, so selective execution can save time and resources.

Ansible tags are also very flexible because you can tag certain tasks or roles and do the opposite of running them. You can skip specific tasks you believe might be problematic to run at the time of execution.

Tags can also help you reuse certain components from your playbook across different scenarios, making them a powerful tool if used correctly.

How to use tags in Ansible?

Now that we know how beneficial Ansible tags can be, let’s review a couple of example use cases. We can assign tags to tasks, roles, or plays within our playbook. 

Adding a tag to a task

In the following example, we are tagging tasks in our playbook. These tasks are responsible for installing Apache and starting the Apache service:

---
- name: Apache
  hosts: test
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
      tags: install_apache

    - name: Start Apache Service
      service:
        name: apache2
        state: started
      tags: start_service

With the following command, we can trigger only the Install Apache tagged task: 

ansible-playbook playbook.yml --tags "install_apache"

And if we run this one, we can just start the Apache service without involving the Install Apache tagged task:

ansible-playbook playbook.yml --tags "start_service"

Adding a tag to a Role

Tags work in the same way for Ansible Roles.

---
- name: Role Playbook
  hosts: test
  roles:
    - role: standard_linux_package
      tags: standard_package

    - role: tomcat_app
      tags: tomcat

You can avoid using the standard Linux package by running the following command:

ansible-playbook playbook.yml --tags "tomcat" 

How to list all tags in Ansible?

Complex playbooks will involve multiple tasks or roles, and it might be cumbersome to go through the entire playbook to discover which tags are being used for which tasks. Listing all tags in an Ansible playbook will show which parts of the playbook can be selectively executed or skipped. 

When running the playbook command, you can use the --list-tags flag to list all the tags associated with tasks, roles, and plays in your playbook.

---
- name: Apache
  hosts: test
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
      tags:
        - install

    - name: Start Apache Service
      service:
        name: apache2
        state: started
      tags:
        - start

    - name: Configure Apache
      template:
        src: apache.conf.j2
        dest: /etc/apache2/apache2.conf
      tags:
        - configure

Run the following command to list all the task tags available in this playbook:

ansible-playbook apache-playbook.yml --list-tags

And you should get an output similar to the one below:

playbook: apache_playbook.yml

  play #1 (all): Apache	TAGS: []
    task #1: Install Apache	TAGS: [install]
    task #2: Start Apache Service	TAGS: [start]
    task #3: Configure Apache	TAGS: [configure]

Using multiple tags in Ansible

Adding multiple tags throughout each task, role, and play can increase the flexibility of your Ansible playbook and give you more control over the execution. This can be useful when you have multiple tasks that can be grouped into one category, and you want to control some of those tasks with a different tag for another purpose. 

With the multiple tagging feature, you can run the Ansible command with the tag flag for any situation to ensure the correct tasks, roles, or plays are executed. 

In the following example, we are grouping some of these tasks as web servers and a few others as db-related. Those tasks are also being tagged with the tags we have been using so far for installing the app, starting the app service, and configuring the app:

---
- name: MyWebApp
  hosts: test
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
      tags:
        - install
        - webserver

    - name: Install MySQL
      apt:
        name: mysql-server
        state: present
      tags:
        - install
        - database

    - name: Configure Apache
      template:
        src: apache.conf.j2
        dest: /etc/apache2/apache2.conf
      tags:
        - configure
        - webserver

    - name: Configure MySQL
      template:
        src: my.cnf.j2
        dest: /etc/mysql/my.cnf
      tags:
        - configure
        - database

    - name: Start Apache Service
      service:
        name: apache2
        state: started
      tags:
        - start
        - webserver

    - name: Start MySQL Service
      service:
        name: mysql
        state: started
      tags:
        - start
        - database

Now, we can deploy the Apache web server without worrying about touching the database and vice versa. We have the flexibility to run the following commands for different scenarios:

ansible-playbook myapp.yml --tags "install"

ansible-playbook myapp.yml --tags "webserver"

ansible-playbook myapp.yml --tags "database"

We can also call multiple tags in the Ansible command:

ansible-playbook example_playbook.yml --skip-tags "configure"

How to skip tags in playbooks?

Skipping tags in playbooks can be useful when there are specific tasks you don’t want to run while allowing the rest of your playbook to execute as planned. By specifying the tags to skip, you can control which tasks are omitted during execution.

For example, during a routine deployment, you might want to apply new configuration changes without restarting the service. By applying tags on the restart service task, you can use the --skip-tags flag with that tag to avoid restarting the service:

---
- name: Update Configuration
  hosts: all
  tasks:
    - name: Apply new configuration
      template:
        src: new_config.j2
        dest: /etc/myapp/config.conf
      tags: update_config

    - name: Restart myapp service
      service:
        name: myapp
        state: restarted
      tags: restart_service

Run the following to skip the tag restart_service:

ansible-playbook update_config.yml --skip-tags "restart_service"

You can also use tagging for specific environments (dev, qa, uat, prod) and use the --skip-tags flag to avoid running certain tasks in certain environments. 

For example, if you want to skip running specific tasks in any environment that is non-dev: 

---
- name: App Deployment
  hosts: all
  tasks:
    - name: Install app dependencies
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - curl
      tags: install_dependencies

    - name: Deploy application code
      git:
        repo: 'https://github.com/me/app.git'
        dest: /var/www/myapp
      tags: deploy_code

    - name: Configure application
      template:
        src: app_config.j2
        dest: /etc/myapp/config.conf
      tags:
        - configure_app
        - skip_in_production

    - name: Restart application service
      service:
        name: app
        state: restarted
      tags:
        - restart_service
        - skip_in_production

    - name: Run database migrations
      command: /usr/local/bin/migrate_db.sh
      tags:
        - migrate_db
        - skip_in_production

Specify your inventory file in the following way:

[dev]
dev.app.com

[qa]
qa.app.com

[uat]
uat.app.com

[production]
prod.app.com

And lastly, execute to the non-dev environments as the following:

ansible-playbook manage_app.yml --limit production --skip-tags "skip_in_production"

ansible-playbook manage_app.yml --limit qa --skip-tags "skip_in_production"

ansible-playbook manage_app.yml --limit uat --skip-tags "skip_in_production"

Depending on the situation, the –skip-tags flag can offer considerable flexibility.

The ‘Always’ and ‘Never’ tags

Two special Ansible tags ensure consistency and stability in your playbook:

  • Thealways’ tag guarantees the task will run regardless of what tag is passed in from the command line. This is useful when critical validation steps, environment setup tasks, or cleanup operation tasks need to run constantly. 
  • Thenever’ tag does the opposite. It ensures that tasks will not run regardless of which tag is being passed down. This is useful for tasks that are being deprecated and should not be run anymore. You can also use the ‘never’ tag if you want to temporarily disable certain tasks during testing or debugging. 

The following example will deploy the application code and config file, but regardless of the tag specified, it will always clean up any temporary files. 

---
- name: App
  hosts: all
  tasks:
    - name: Deploy application
      git:
        repo: 'https://github.com/app/myapp.git'
        dest: /var/www/myapp
      tags: deploy_git_code

    - name: Configure application
      template:
        src: app_config.j2
        dest: /etc/myapp/config.conf
      tags: configure_app

    - name: Cleanup temporary files
      file:
        path: /tmp/myapp_temp
        state: absent
      tags: always

The next example focuses on a situation where you want to avoid running deprecated tasks. If anyone runs this playbook with the db_update tag, the “Update database schema” task will not run because it has the never flag associated with it:

---
- name: Update Database Schema
  hosts: db_servers
  tasks:
    - name: Backup database
      command: /usr/local/bin/db_backup.sh
      tags: db_backup

    - name: Update database schema (deprecated)
      command: /usr/local/bin/db_update.sh
      tags: 
        - db_update
        - never

    - name: Reboot database server
      reboot:
      tags: reboot_db

The always and never tags can be used in the same playbook without conflict. Let’s take a look at an example:

---
- name: Deploy app
  hosts: all
  tasks:
    - name: Validate environment
      command: /usr/local/bin/validate_env.sh
      tags: always

    - name: Deploy application
      git:
        repo: 'https://github.com/app/myapp.git'
        dest: /var/www/myapp
      tags: deploy_app

    - name: Deprecated deployment step
      command: /usr/local/bin/deprecated_deploy.sh
      tags: 
        - deprecated
        - never

    - name: Cleanup environment
      command: /usr/local/bin/cleanup_env.sh
      tags: always

Advanced usage of Ansible tags

This section features examples using Ansible tags with variables, facts, roles, and imports.

Using tags on variables

You can pass in tag values through variables, which allows you to manage all your tags from a single point instead of having them spread out across the playbook.

---
- name: Playbook with Tags and Variables
  hosts: all
  vars:
    install_tag: "install_apache"

  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
      tags: "{{ install_tag }}"

You can also trigger variables only when the tagged value is being called via the command line. 

The following playbook ensures the web_server_port variable gets passed in only when the webserver tag is called. The db_server_port variable gets used within that specific task only when the dbserver tag is called.

- hosts: all
  vars:
    web_server_port: 80
    db_server_port: 5432

  tasks:
    - name: Install web server
      tags: webserver
      yum:
        name: httpd
        state: present

    - name: Start web server
      tags: webserver
      service:
        name: httpd
        state: started

    - name: Configure web server port
      tags: webserver
      lineinfile:
        path: /etc/httpd/conf/httpd.conf
        regexp: '^Listen '
        line: "Listen {{ web_server_port }}"
        notify: restart webserver

    - name: Install database server
      tags: dbserver
      yum:
        name: postgresql-server
        state: present

    - name: Start database server
      tags: dbserver
      service:
        name: postgresql
        state: started

    - name: Configure database server port
      tags: dbserver
      lineinfile:
        path: /var/lib/pgsql/data/postgresql.conf
        regexp: '^port = '
        line: "port = {{ db_server_port }}"
        notify: restart dbserver

  handlers:
    - name: restart webserver
      service:
        name: httpd
        state: restarted

    - name: restart dbserver
      service:
        name: postgresql
        state: restarted

Ansible tags on facts

Ansible Facts are system properties that Ansible gathers from managed nodes when Ansible connects to them. These properties include details about the OS, installed packages, network interfaces, and more. By default, gathering facts runs at the beginning of each playbook run. 

Sometimes, running gather_facts every time the playbook runs can be time-consuming and affect the playbook’s performance. With tags, you have more control over the flow of execution, allowing you to collect data only when a certain tag is specified. 

In the following example, we want only a few tasks to run if gather_facts tag is used and the rest of the tasks to run as usual (Note: gather_facts is specifically set to false since, by default, gather_facts is set to true). 

To use tags to gather Facts, you will need to use the setup module:

---
- name: Gathering facts with tags
  hosts: all
  gather_facts: false
  tasks:
    - name: Gather facts
      setup:
      tags: gather_facts

    - name: Install Apache on Ubuntu
      apt:
        name: apache2
        state: present
      when: ansible_facts['os_family'] == "Debian"
      tags:
        - install_apache

    - name: Install Apache on CentOS
      yum:
        name: httpd
        state: present
      when: ansible_facts['os_family'] == "RedHat"
      tags:
        - install_apache

    - name: Configure Apache on Ubuntu
      template:
        src: apache_ubuntu.conf.j2
        dest: /etc/apache2/apache2.conf
      when: ansible_facts['os_family'] == "Debian"
      tags:
        - configure_apache

    - name: Configure Apache on CentOS
      template:
        src: apache_centos.conf.j2
        dest: /etc/httpd/conf/httpd.conf
      when: ansible_facts['os_family'] == "RedHat"
      tags:
        - configure_apache

    - name: Create a directory
      file:
        path: /tmp/myapp
        state: directory

    - name: Copy application files
      copy:
        src: /local/path/
        dest: /tmp/myapp/

Now, if we want to install Apache on a Debian machine, we can use the following command:

ansible-playbook apache.yml --tags "gather_facts,install_apache"

You can also use tags to avoid running gathering_facts on production systems by using a mix of conditionals and tags. 

In the following example, we specify a variable ansible_env in our inventory file with its dedicated environment. The tag will get triggered to gather facts only when the environment is non-production:

---
- name: Gather facts conditionally
  hosts: all
  gather_facts: false 
  tasks:
    - name: Gather facts in non-prod
      setup:
      tags: gather_facts_non_prod
      when: env != "production"

    - name: Deploy application
      git:
        repo: 'https://github.com/example/myapp.git'
        dest: /var/www/myapp
      tags: deploy_app

This is how we can hardcode the environments in our inventory file:

[development]
dev.company.com env=development

[production]
prod.company.com env=production

You can run the following command, but this will only run against the dev environment because we specified it in our inventory file:

ansible-playbook deployment_playbook.yml --tags "gather_facts_non_prod,deploy_app"

Ansible tags on roles and imports

We covered some simple examples of using roles with tags, but in the next example, we will incorporate using imports along with our roles.

In this example, the playbook has a couple of tagged roles that run only when the specific tags are called. We want to run some extra tasks along with that role in certain situations.

We can structure our playbook to run the roles via tags and also import tasks and call those tasks via tags as well:

---
- name: Deploy App
  hosts: all
  roles:
    - { role: common, tags: ['common'] }
    - { role: webserver, tags: ['webserver'] }
    - { role: database, tags: ['database'] }
  tasks:
    - name: Import additional common tasks
      import_tasks: standard_tasks.yml
      tags: standard_tasks

    - name: Import additional web server tasks
      import_tasks: webserver_tasks.yml
      tags: webserver_tasks

    - name: Import additional database tasks
      import_tasks: database_tasks.yml
      tags: database_tasks

Here is an example of a standard task you could import with your existing role:

---
- name: Install additional common utilities
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - htop
    - tree

- name: Configure timezone
  timezone:
    name: 'Etc/UTC'

- name: Set up NTP service
  apt:
    name: ntp
    state: present

- name: Start and enable NTP service
  service:
    name: ntp
    state: started
    enabled: yes

- name: Update system packages
  apt:
    upgrade: dist

You can trigger a webserver deployment using the ansible role with the standard_tasks and webserver_tasks with the following command:

ansible-playbook deploy_application.yml --tags "webserver,standard_tasks,webserver_tasks"

What are the best practices for Ansible tags?

Here are a few best practices for maintaining tags across your Ansible playbooks, roles, and plays:

Ansible tags best practices
Consistency
  • Follow the naming conventions: Naming your Ansible tags properly will make your playbooks cleaner and easier to understand. You can use “install_”, “configure_”, “uninstall_”, or “deploy_” to ensure everyone knows what the tag is responsible for. 
  • Group-related tasks: Tag all related tasks to make it easier to execute a group of tasks without having to remember the individual task names. For example, all database-related tasks have a “data_setup” tag associated with them. 
Documentation
  • Add comments: If the task or role is unclear, add comments with your tags. This will help other team members understand its true purpose. 
  • Create a tag reference document: This separate document will list all the tags used across your playbooks with descriptions, allowing teams to maintain consistency.
  • Follow tag usage guidelines: It is crucial to establish guidelines amongst the team regarding how your tags will be structured and used. Include examples and scenarios of where tags should be applied. 
Avoid Overusing Tags 
  • Limit the number of tags per task: Avoid using multiple tags for a specific task. Only use multiple tags when needed. Limit to 2-3 tags per task. 
  • Use tags when needed: Not every task needs a tag; only use them for tasks that can run independently. 
  • Avoid redundant tags: Each tag should be unique. Avoid creating multiple tags that have the same purpose. 
  • Review your existing tags: Periodically review your tags to ensure they are still relevant, and avoid stale tags. 

Ansible tags in real-world examples

In this section, we will cover some possible real-world examples to illustrate how you can utilize tags across your Ansible playbooks.

Example 1: Database maintenance

To control the maintenance of your database and manage specific tasks within your playbook independently —  instead of running the entire playbook each time — you can use tags as shown below:

---
- name: Database Maintenance
  hosts: db_servers
  tasks:
    - name: Backup database
      command: /usr/local/bin/db_backup.sh
      tags: 
        - backup

    - name: Update database schema
      command: /usr/local/bin/db_update.sh
      tags: 
        - update

    - name: Restart database service
      service:
        name: postgresql
        state: restarted
      tags: 
        - restart

To perform a backup, you can run the following:

ansible-playbook db_maintenance.yml --tags "backup"

To perform an update, run the following:

ansible-playbook db_maintenance.yml --tags "update"

To restart the database service, run:

ansible-playbook db_maintenance.yml --tags "restart"

Example 2: Application deployment to different environments

In this scenario, we want to condense our playbooks into a single playbook that will be responsible for deploying the application to different environments. We can control environmental deployments using tags as the following:

---
- name: App Deployment
  hosts: all
  tasks:
    - name: Install dependencies
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - python3-pip
        - git
      tags: 
        - install
        - dev
        - staging
        - prod

    - name: Deploy app in dev
      git:
        repo: 'https://github.com/app/dev_app.git'
        dest: /var/www/myapp
      tags: 
        - deploy
        - dev

    - name: Deploy app in staging
      git:
        repo: 'https://github.com/app/stage_app.git'
        dest: /var/www/myapp
      tags: 
        - deploy
        - staging

    - name: Deploy app in prod
      git:
        repo: 'https://github.com/app/prod_app.git'
        dest: /var/www/myapp
      tags: 
        - deploy
        - prod

    - name: Start app service
      service:
        name: myapp
        state: started
      tags: 
        - dev
        - staging
        - prod

You can trigger the dev deployment by running the following:

ansible-playbook app.yml --tags "install,deploy,dev"

Similarly, you can trigger the other environments:

ansible-playbook app.yml --tags "install,deploy,staging"
ansible-playbook app.yml --tags "install,deploy,prod"

Example 3: Rolling updates in high-availability environments

Performing rolling updates can ensure less downtime for your application. The example below will give you more control over how your application is updated.

---
- name: Rolling Update for Web Servers
  hosts: web_servers
  serial: 1
  tasks:
    - name: Take server out of load balancer
      command: /usr/local/bin/remove_from_lb.sh
      tags: 
        - lb_remove

    - name: Update application code
      git:
        repo: 'https://github.com/app/myapp.git'
        dest: /var/www/myapp
      tags: 
        - update_code

    - name: Restart web server
      service:
        name: apache2
        state: restarted
      tags: 
        - restart

    - name: Add server back to load balancer
      command: /usr/local/bin/add_to_lb.sh
      tags: 
        - lb_add

To remove the server from a load balancer, run the following:

ansible-playbook rolling_update.yml --tags "lb_remove"

To update the application code only, you can run:

ansible-playbook rolling_update.yml --tags "update_code"

To restart the web server, run the following: 

ansible-playbook rolling_update.yml --tags "restart"

Example 4: Managing multiple services

Tags can be very useful if you want to use one playbook to manage multiple services, such as web servers, application servers, and database servers. With tags, you can control running different parts of the playbook:

---
- name: Managing Multi-Service
  hosts: all
  tasks:
    - name: Install web server
      apt:
        name: apache2
        state: present
      tags: 
        - install
        - webserver

    - name: Install app server
      apt:
        name: tomcat
        state: present
      tags: 
        - install
        - appserver

    - name: Install db server
      apt:
        name: postgresql
        state: present
      tags: 
        - install
        - dbserver

    - name: Configure web server
      template:
        src: apache.conf.j2
        dest: /etc/apache2/apache2.conf
      tags: 
        - configure
        - webserver

    - name: Configure app server
      template:
        src: tomcat.conf.j2
        dest: /etc/tomcat/tomcat.conf
      tags: 
        - configure
        - appserver

    - name: Configure db server
      template:
        src: postgresql.conf.j2
        dest: /etc/postgresql/postgresql.conf
      tags: 
        - configure
        - dbserver

    - name: Start web server service
      service:
        name: apache2
        state: started
      tags: 
        - start
        - webserver

    - name: Start app server service
      service:
        name: tomcat
        state: started
      tags: 
        - start
        - appserver

    - name: Start db server service
      service:
        name: postgresql
        state: started
      tags: 
        - start
        - dbserver

To simply install and configure your web server, run:

ansible-playbook multi_service.yml --tags "install,configure,webserver"

To start your app server, you can run the following:

ansible-playbook multi_service.yml --tags "start,appserver"

To perform a full setup and start the database server, you can run:

ansible-playbook multi_service.yml --tags "install,configure,start,dbserver"

How can Spacelift help you with Ansible projects?

Spacelift’s vibrant ecosystem and excellent GitOps flow can greatly assist you in managing and orchestrating Ansible. By introducing Spacelift on top of Ansible, you can then easily create custom workflows based on pull requests and apply any necessary compliance checks for your organization.

Another advantage of using Spacelift is that you can manage different infrastructure tools like Ansible, OpenTofu, Terraform, Pulumi, AWS CloudFormation, and even Kubernetes from the same place and combine their stacks with building workflows across tools. Spacelift greatly simplifies and elevates your workflow for all of these tools, and the ability to create dependencies between stacks and passing outputs enables you to make end-to-end deployments with a small change.

If you want to learn more about using Spacelift with Ansible, check our documentation, read our Ansible guide, or book a demo with one of our engineers.

Key points

Ansible tags provide an efficient and flexible way to manage and control the execution of tasks, roles, and plays within your Ansible playbooks. Whether you need to perform targeted updates, manage multiple services, or conduct rolling updates in high-availability environments, tags enhance your ability to maintain and optimize your infrastructure with precision and ease. 

Adopting best practices for tagging, such as consistent naming conventions and proper documentation, ensures that your playbooks remain organized and effective, making Ansible tags a powerful tool for any IT automation strategy.

Manage Ansible Better with Spacelift

Spacelift helps you manage the complexities and compliance challenges of using Ansible. It brings with it a GitOps flow, so your infrastructure repository is synced with your Ansible Stacks, and pull requests show you a preview of what they’re planning to change. It also has an extensive selection of policies, which lets you automate compliance checks and build complex multi-stack workflows.

Learn More

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide