What are tags in Ansible and how to use them

Posted on April 14, 2021 by Adrian Wyssmann ‐ 8 min read

Sometimes you have playbooks or roles which you only want to execute parts of it and not all. This is where you can use ansible tags.

With Ansible Tags you can execute or skip selected tasks. I will try to summarize and not repeat the official documentation, cause it’s quite well explained and documented there.

How does it work

There are two things you have to do

  • Add tags to your tasks, either individually or to a block, play, role, or import (which will inherited)
  • Select or skip tags when you run your playbook.

As mentioned in Ansible Tags uses the tag-element to each task, like this

tasks:
- name: Install the servers
  ansible.builtin.yum:
    name:
    - httpd
    - memcached
    state: present
  tags:
  - packages
  - webservers

- name: Configure the service
  ansible.builtin.template:
    src: templates/src.j2
    dest: /etc/foo.conf
  tags:
  - configuration

If you don’t want to add tags for each task you can take advantage of inheritance. This means tags which are defined on a level of block, playbook, roles or imports, are then applied to all lower level elements. In the example below, we define tag ntp in the block element. All tasks belonging to that block implicitly also are tagged with ntp:

tasks:
- block:
  tags: ntp
  - name: Install ntp
    ansible.builtin.yum:
      name: ntp
      state: present

  - name: Configure ntp
    ansible.builtin.template:
      src: ntp.conf.j2
      dest: /etc/ntp.conf
    notify:
    - restart ntpd

  - name: Enable and run ntpd
    ansible.builtin.service:
      name: ntpd
      state: started
      enabled: yes

- name: Install NFS utils
  ansible.builtin.yum:
    name:
    - nfs-utils
    - nfs-util-lib
    state: present
  tags: filesharing

Also worth to mention are the special, reserved tags always, never and debug. Tasks or play tagged with always will always run except you skip it with --skip-tags always. The opposite is with the never tag - tasks and plays will only run if you explicitly allow with --tag never.s At last, the debug tag is also a tag which will only run with --tag debug.

How to use tags

The usage of tags is quite easy. Once you have added tags to your tasks, includes, blocks, plays, roles, and imports, you can selectively execute or skip tasks based on their tags when you run ansible-playbook. Use the following command line arguments:

  • --tags all - run all tasks, ignore tags (default behavior)
  • --tags [tag1, tag2] - run only tasks with the tags tag1 and tag2
  • --skip-tags [tag3, tag4 - run all tasks except those with the tags tag3 and tag4
  • --tags tagged - run only tasks with at least one tag
  • --tags untagged - run only tasks with no tags
  • --list-tags - generate a list of available tags
  • --list-tasks - when used with --tags tagname or --skip-tags tagname, generate a preview of tagged tasks

My use-case - an example

I had some playbooks which could perform the initial setup (bootstrapping) of my Hetzner Cloud environment. However, I have chosen to move the generic functionality into a “helper”-role which is structured as follows:

hcloud
├── defaults
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
└── tasks
    ├── hcloud_firewall.yml
    ├── hcloud_networks.yml
    ├── hcloud_project.yml
    ├── hcloud_server.yml
    └── main.yml

This role Setup and configure Hetzner Cloud

  • add ssh keys
  • add firewalls
  • add networks
  • create a server and assign it the respective firewalls and networks

It requires a variable hcloud_env - a dictionary which contains a list of named “environments” as follows:

hcloud_env:
  development:
    api_token: XXXXXX
    firewalls:
      firewall-ssh-only:
        - direction: "in"
          port: "22"
          protocol: "tcp"
          source_ips:
            - 0.0.0.0/0
            - ::/0
      firewall-no-access:
    networks:
      network-private-dev:
        ip_range: "10.10.0.0/16"
        subnets:
          - ip_range: "10.10.0.0/24"
            network_zone: "eu-central"
            type: "cloud"
  production:
    api_token: XXXXXX
    firewalls:
      firewall-ssh-only:
        - direction: "in"
          port: "22"
          protocol: "tcp"
          source_ips:
            - 0.0.0.0/0
            - ::/0
      firewall-no-access:
    networks:
      network-private-prd:
        ip_range: "10.10.0.0/16"
        subnets:
          - ip_range: "10.10.0.0/24"
            network_zone: "eu-central"
            type: "cloud"

The first part of main.yml iterates over the projects and does creates the related elements:

- name: Setup Hetzner Cloud project
  include_tasks: hcloud_project.yml
  loop: "{{ hcloud_env|dict2items }}"
  loop_control:
    loop_var: hcloud_project
  tags:
    - hcloud
    - network
    - hcloud-bootstrap

- name: Create a server instance in the Hetzner cloud
  include_tasks: hcloud_server.yml
  tags:
    - hcloud
    - hcloud-server

So as you can see I have tagged the two steps with different tags. This allows me to achieve the following

  • do the initial setup of the cloud (which has to be done once and not every time I add a new server). So the include_tasks: hcloud_project.yml will run when I use the tag hcloud-bootstrap
  • create servers - the include_tasks: hcloud_server.yml will run when I use the tag hcloud-server

Ok, I believe this is a bit an abnormal case, but still it’s very useful for me and shows you what you can achieve with tags.

Initial setup using tag hcloud-bootstrap

So this is my playbook hcloud_bootstrap.yml which uses the role hcloud mentioned above:

- hosts: localhost
  become: no
  gather_facts: false

  roles:
  - role: hcloud

I run this play with --tags hcloud-bootstrap:

ansible-playbook bootstrap-hcloud.yml --tags hcloud-bootstrap

This will apply role hcloud but skip everything which is not tagged with hcloud-bootstrap:

PLAY [localhost] ****************************************************************************************************************************************************************************************************

TASK [hcloud : Setup Hetzner Cloud projects] ************************************************************************************************************************************************************************
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_project.yml for localhost => (item={'key': 'development', 'value': {'api_token': 'xxx', 'firewalls': {'firewall-ssh-only': [{'direction': 'in', 'port': '22', 'protocol': 'tcp', 'source_ips': ['0.0.0.0/0', '::/0']}], 'firewall-no-access': None}, 'networks': {'network-private-dev': {'ip_range': '10.10.0.0/16', 'subnets': [{'ip_range': '10.10.0.0/24', 'network_zone': 'eu-central', 'type': 'cloud'}]}}}})
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_project.yml for localhost => (item={'key': 'production', 'value': {'api_token': 'xxx', 'firewalls': {'firewall-ssh-only': [{'direction': 'in', 'port': '22', 'protocol': 'tcp', 'source_ips': ['0.0.0.0/0', '::/0']}], 'firewall-no-access': None}, 'networks': {'network-private-prd': {'ip_range': '10.10.0.0/16', 'subnets': [{'ip_range': '10.10.0.0/24', 'network_zone': 'eu-central', 'type': 'cloud'}]}}}})

TASK [hcloud : development: Set api token] **************************************************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : development: Add ssh-keys] ***************************************************************************************************************************************************************************
ok: [localhost] => (item={'key': 'ssh-key1', 'value': 'ssh-rsa xxx'})
ok: [localhost] => (item={'key': 'ssh-key2', 'value': 'ssh-rsa xxx'})
ok: [localhost] => (item={'key': 'ssh-key3', 'value': 'ssh-rsa xxx'})

TASK [hcloud : development: Create firewalls] ***********************************************************************************************************************************************************************
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_firewall.yml for localhost => (item={'key': 'firewall-ssh-only', 'value': [{'direction': 'in', 'port': '22', 'protocol': 'tcp', 'source_ips': ['0.0.0.0/0', '::/0']}]})
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_firewall.yml for localhost => (item={'key': 'firewall-no-access', 'value': None})

TASK [hcloud : development: Firewall firewall-ssh-only] *************************************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : development: Firewall 'firewall-ssh-only' with no rules] *********************************************************************************************************************************************
skipping: [localhost]

TASK [hcloud : development: Firewall firewall-no-access] ************************************************************************************************************************************************************
skipping: [localhost]

TASK [hcloud : development: Firewall 'firewall-no-access' with no rules] ********************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : development: Create networks and corresponding subnets] **********************************************************************************************************************************************
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_networks.yml for localhost => (item={'key': 'network-private-dev', 'value': {'ip_range': '10.10.0.0/16', 'subnets': [{'ip_range': '10.10.0.0/24', 'network_zone': 'eu-central', 'type': 'cloud'}]}})

TASK [hcloud : development: Create a network 'network-private-dev'] *************************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : {{ hcloud_project.key }}: Create a subnetwork '{{ subnet.ip_range }}' for '{{ network.key }}'] *******************************************************************************************************
ok: [localhost] => (item={'ip_range': '10.10.0.0/24', 'network_zone': 'eu-central', 'type': 'cloud'})

TASK [hcloud : production: Set api token] ***************************************************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : production: Add ssh-keys] ****************************************************************************************************************************************************************************
ok: [localhost] => (item={'key': 'ssh-key1', 'value': 'ssh-rsa xxx'})
ok: [localhost] => (item={'key': 'ssh-key2', 'value': 'ssh-rsa xxx'})
ok: [localhost] => (item={'key': 'ssh-key3', 'value': 'ssh-rsa xxx'})

TASK [hcloud : production: Create firewalls] ************************************************************************************************************************************************************************
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_firewall.yml for localhost => (item={'key': 'firewall-ssh-only', 'value': [{'direction': 'in', 'port': '22', 'protocol': 'tcp', 'source_ips': ['0.0.0.0/0', '::/0']}]})
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_firewall.yml for localhost => (item={'key': 'firewall-no-access', 'value': None})

TASK [hcloud : production: Firewall firewall-ssh-only] **************************************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : production: Firewall 'firewall-ssh-only' with no rules] **********************************************************************************************************************************************
skipping: [localhost]

TASK [hcloud : production: Firewall firewall-no-access] *************************************************************************************************************************************************************
skipping: [localhost]

TASK [hcloud : production: Firewall 'firewall-no-access' with no rules] *********************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : production: Create networks and corresponding subnets] ***********************************************************************************************************************************************
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_networks.yml for localhost => (item={'key': 'network-private-prd', 'value': {'ip_range': '10.10.0.0/16', 'subnets': [{'ip_range': '10.10.0.0/24', 'network_zone': 'eu-central', 'type': 'cloud'}]}})

TASK [hcloud : production: Create a network 'network-private-prd'] **************************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : {{ hcloud_project.key }}: Create a subnetwork '{{ subnet.ip_range }}' for '{{ network.key }}'] *******************************************************************************************************
ok: [localhost] => (item={'ip_range': '10.10.0.0/24', 'network_zone': 'eu-central', 'type': 'cloud'})

PLAY RECAP **********************************************************************************************************************************************************************************************************
localhost                  : ok=20   changed=0    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0  

Create servers using tag hcloud-server

I use the same role hcloud in a different playbook hcloud_bootstrap_server.yml

# This playbook shall only run against new servers thus you have to specify a coma separated list of server names {{ server_names }}
- hosts: localhost
  become: no
  gather_facts: false
  vars:
    hcloud_server_project: "development"
    hcloud_server_image: "debian-10"
    hcloud_server_type: "cx21"
    hcloud_server_network: "network-private-dev"
    hcloud_firewalls:
    - "firewall-ssh-only"
    - "firewall-no-access"
    hcloud_server_sshkeys:
    - ssh-key1
    - ssh-key2
    hcloud_server_labels:
      dev: ""

  roles:
  - role: hcloud

# before connecting ensure to wait the hosts are ready
- hosts: "{{ server_names }}"
  gather_facts: false
  vars:
    ansible_host: "{{ server_ipv4 }}"
  tags:
    - hcloud-server

  pre_tasks:
  - name: Waits until server is ready
    wait_for:
      host: "{{ server_ipv4 }}"
      port: 22
      delay: 2
    delegate_to: localhost
    tags:
      - hcloud-server

- hosts: "{{ server_names }}"
  vars:
    ansible_host: "{{ server_ipv4 }}"
    ansible_ssh_user: "{{ bootstrap_remote_user }}"
  tags:
    - hcloud-server

  roles:
  - role: base-system
    vars:
      ansible_host: "{{ server_ipv4 }}"
    tags:
      - hcloud-server
  - role: papanito.cloudflared
    tags:
      - hcloud-server

Which I run with --tags hcloud-server plus -e server_names=x1,x2, which are the server names for the servers to be created:

ANSIBLE_HOST_KEY_CHECKING=False  ansible-playbook bootstrap-hcloud_servers.yml -e server_names="dev0001" --tags hcloud-server  -e 'hcloud_server_labels={"dev":"","k8s":"master"}'

Which will apply role hcloud but skip everything which is not tagged with hcloud-server:

PLAY [localhost] ****************************************************************************************************************************************************************************************************

TASK [hcloud : Create a server instance in the Hetzner cloud] *******************************************************************************************************************************************************
included: /home/aedu/Workspaces/example.com/infrastructure/roles/hcloud/tasks/hcloud_server.yml for localhost

TASK [hcloud : Set api token for server creation] *******************************************************************************************************************************************************************
ok: [localhost]

TASK [hcloud : {{ hcloud_server_project }}: Create a server '{{ server_name }}'] ************************************************************************************************************************************
changed: [localhost] => (item=dev0001)

TASK [hcloud : {{ hcloud_server_project }}: Create a server network for '{{ server_name }}'] ************************************************************************************************************************
changed: [localhost] => (item=dev0001)

PLAY [dev0001] ******************************************************************************************************************************************************************************************************

TASK [Waits until server is ready] **********************************************************************************************************************************************************************************
ok: [dev0001]

PLAY [dev0001] ******************************************************************************************************************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************************************************************
ok: [dev0001]

TASK [base-system : base-system | users | Ensure group "ansible" exists] ********************************************************************************************************************************************
changed: [dev0001]
...

PLAY RECAP **********************************************************************************************************************************************************************************************************
dev0001                    : ok=29   changed=14   unreachable=0    failed=0    skipped=16   rescued=0    ignored=0   
localhost                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

I hope this makes sense to you and may be helpful to understand tags a bit better.