Ansible Lesson 22 – Tags and Selective Execution | Dataplexa
Section II · Lesson 22

Tags and Selective Execution

In this lesson

Assigning tags --tags & --skip-tags Tag inheritance Special tags Tag strategy

Tags are labels that you attach to tasks, plays, roles, or includes in a playbook — and then use at the command line to run only the labelled subset of tasks, or to skip it entirely. They exist to solve a real problem: as playbooks grow, running the entire thing for every small change becomes slow and unnecessarily risky. Tags let you run only the config tasks when you change a config file, only the packages tasks when you add a dependency, or only the deploy tasks when you push a new release — without splitting your automation into dozens of separate playbooks.

Assigning Tags

Tags are assigned using the tags: attribute. Any task, play, block, role, or include can receive one or more tags. A task with multiple tags will run whenever any of its tags is selected — tags use OR logic, not AND.

---
- name: Provision and configure web server
  hosts: webservers
  become: true
  tags: webservers        # tag applied to the entire play

  tasks:

    # Single tag
    - name: Install Nginx
      ansible.builtin.package:
        name: nginx
        state: present
      tags: packages

    # Multiple tags — task runs if EITHER tag is selected
    - name: Deploy Nginx configuration
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Reload Nginx
      tags:
        - config
        - nginx

    # Tags on a block — inherited by all tasks inside
    - name: Security hardening tasks
      tags: security
      block:
        - name: Disable SSH root login
          ansible.builtin.lineinfile:
            path: /etc/ssh/sshd_config
            regexp: "^PermitRootLogin"
            line: "PermitRootLogin no"

        - name: Set restrictive umask
          ansible.builtin.lineinfile:
            path: /etc/profile
            line: "umask 027"

    # Tags on individual tasks within a block
    - name: Deploy application
      tags: deploy
      block:
        - name: Create deploy directory
          ansible.builtin.file:
            path: /var/www/app
            state: directory
          tags: [deploy, setup]      # inline list format

        - name: Copy application files
          ansible.builtin.copy:
            src: app/
            dest: /var/www/app/
          tags: deploy

  handlers:
    - name: Reload Nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded
      tags: nginx

Running Playbooks with Tags

Tags are used at the command line with --tags to run only tagged tasks, or --skip-tags to run everything except the tagged tasks. Both flags accept comma-separated lists of tag names.

# Run ONLY tasks tagged 'packages'
ansible-playbook site.yml --tags packages

# Run ONLY tasks tagged 'config' or 'nginx' (OR logic)
ansible-playbook site.yml --tags "config,nginx"

# Run everything EXCEPT tasks tagged 'security'
ansible-playbook site.yml --skip-tags security

# Run config and deploy tasks, skip packages
ansible-playbook site.yml --tags "config,deploy" --skip-tags packages

# List all tags in a playbook without running anything
ansible-playbook site.yml --list-tags

# List all tasks that would run with a given tag
ansible-playbook site.yml --tags config --list-tasks
# Output of: ansible-playbook site.yml --list-tags

playbook: site.yml

  play #1 (webservers): Provision and configure web server  TAGS: [webservers]
      TASK TAGS: [config, deploy, nginx, packages, security, setup, webservers]

# Output of: ansible-playbook site.yml --tags config --list-tasks

playbook: site.yml

  play #1 (webservers): Provision and configure web server  TAGS: [webservers]
    tasks:
      Deploy Nginx configuration    TAGS: [config, nginx]

What just happened?

--list-tags shows every tag defined in the playbook without executing anything — useful for discovering what tags exist before running. --list-tasks combined with --tags previews exactly which tasks would run — the dry-run equivalent for tag-based selection. Always run both before using tags in production for the first time.

The Highlight Reel Analogy

Tags work like chapters in a documentary film. The full playbook is the entire film — comprehensive and complete. Tags are the chapter markers that let you jump directly to the section you need right now. You do not re-watch the entire three-hour documentary to see one five-minute scene. Similarly, you do not run the entire provisioning playbook to update a single config file. Tags let you go straight to the relevant chapter.

Tag Inheritance

Tags cascade downward through the playbook hierarchy. A tag on a play applies to every task in that play. A tag on a block applies to every task in that block. A tag on a role applies to every task in that role. A task inherits all tags from every level above it — and can also have its own additional tags.

Play tags: [webservers] inherits: — effective: [webservers] Block tags: [security] inherits: [webservers] effective: [webservers, security] Task: Disable SSH root login tags: [ssh] (own tag) effective: [webservers, security, ssh] Task: Set restrictive umask tags: [] (no own tag) effective: [webservers, security]

The practical implication: if you tag an entire play with webservers, running --tags webservers runs every task in that play. If you only want the security tasks, run --tags security. If you want webservers tasks that are also security-related, run --tags "webservers,security" — which selects tasks matching either tag.

Special Built-in Tags

Ansible has four built-in tag values with special meanings. They are reserved — do not use them as names for your own tags, as their behaviour may be unexpected.

Special

always

A task tagged always runs every time the playbook runs — even when you specify --tags that do not include it. Use for essential setup tasks like gathering facts or creating directories that other tasks depend on.

Special

never

A task tagged never is skipped unless you explicitly request it with --tags never. Use for dangerous or destructive tasks — factory resets, data purges, or debug tasks that should only run by explicit choice.

Special

tagged

Selects all tasks that have any tag at all — equivalent to running every tagged task while skipping untagged ones. Useful for running "everything that was labelled" without listing individual tag names.

Special

untagged

Selects only tasks that have no tags. Combined with --tags untagged, runs all tasks that were left untagged — useful for finding tasks that were forgotten when tagging a playbook.

# always — runs every time, even when using --tags
- name: Gather application facts
  ansible.builtin.include_vars:
    file: "{{ environment }}.yml"
  tags: always             # must always run — other tasks depend on these vars

# never — skipped by default, only runs with explicit --tags never
- name: Factory reset — DELETE ALL DATA
  ansible.builtin.file:
    path: "{{ data_dir }}"
    state: absent
  tags: never              # only run when explicitly requested with --tags never

# Practical: debug task hidden from normal runs
- name: Dump all variables for debugging
  ansible.builtin.debug:
    var: hostvars[inventory_hostname]
  tags:
    - never
    - debug               # run with --tags debug during troubleshooting

Tag Strategy — What to Tag and How

A good tag strategy makes a playbook significantly more useful in day-to-day operations. A bad tag strategy creates complexity without benefit — or worse, makes the playbook unpredictable when run with tag subsets. The rules below represent the community consensus on tag naming and granularity.

Tag strategy rules

Tag by function Use functional categories: packages, config, services, users, security, deploy. These map to the natural groups of tasks in any playbook.
Tag by component Add component tags alongside function tags: nginx, postgres, docker. This allows targeting all nginx-related tasks (config, service, packages) in one command.
Keep tags flat Do not create hierarchical tags like nginx:config or security/ssh. Use multiple simple tags per task instead: tags: [nginx, config].
Document your tags List your tag conventions in the project README.md so other engineers know which tags exist and what subset each one runs. A tag strategy is only useful if the team knows about it.
Always tag handlers If a task with a tag notifies a handler, tag the handler with the same tag — otherwise the handler is skipped when you run a subset of tasks, potentially leaving a service in a stale state.

Practical Tagging Example

The scenario: A team maintains a site.yml that provisions a full web server. They need to be able to run specific subsets daily: only push updated configs after a config review, only deploy new application releases, or only re-apply security settings after an audit. Below is a well-tagged version of that playbook and the commands each team member uses.

---
- name: Full web server provisioning
  hosts: webservers
  become: true

  tasks:

    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 3600
      tags: [packages, always]     # always run — other tasks depend on fresh cache

    - name: Install required packages
      ansible.builtin.package:
        name: "{{ item }}"
        state: present
      loop: "{{ web_packages }}"
      tags: packages

    - name: Deploy Nginx config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        validate: "nginx -t -c %s"
      notify: Reload Nginx
      tags: [config, nginx]

    - name: Deploy virtual host configs
      ansible.builtin.template:
        src: "{{ item }}.conf.j2"
        dest: "/etc/nginx/conf.d/{{ item }}.conf"
      loop: "{{ vhosts }}"
      notify: Reload Nginx
      tags: [config, nginx, vhosts]

    - name: Deploy application release
      ansible.builtin.unarchive:
        src: "{{ release_url }}"
        dest: /var/www/app
        remote_src: false
      tags: deploy

    - name: Restart application service
      ansible.builtin.service:
        name: myapp
        state: restarted
      tags: [deploy, services]

    - name: Disable SSH password authentication
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^PasswordAuthentication"
        line: "PasswordAuthentication no"
      notify: Restart SSH
      tags: [security, ssh]

    - name: Configure fail2ban
      ansible.builtin.template:
        src: fail2ban.conf.j2
        dest: /etc/fail2ban/jail.local
      notify: Restart fail2ban
      tags: [security, fail2ban]

    - name: Dump all vars (debug only)
      ansible.builtin.debug:
        var: hostvars[inventory_hostname]
      tags: [never, debug]

  handlers:
    - name: Reload Nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded
      tags: [config, nginx]        # must match the tasks that notify it

    - name: Restart SSH
      ansible.builtin.service:
        name: sshd
        state: restarted
      tags: [security, ssh]

    - name: Restart fail2ban
      ansible.builtin.service:
        name: fail2ban
        state: restarted
      tags: [security, fail2ban]

How the team uses the tags

# After updating nginx.conf.j2 — redeploy config only
ansible-playbook site.yml --tags config

# After a new application release is ready
ansible-playbook site.yml --tags deploy

# Monthly security audit — re-apply all security settings
ansible-playbook site.yml --tags security

# Adding a new package to web_packages list
ansible-playbook site.yml --tags packages

# Push new vhost config files only
ansible-playbook site.yml --tags vhosts

# Debug: dump all variables on a single host
ansible-playbook site.yml --tags debug --limit web01.example.com

# Full provisioning run (new server) — run everything
ansible-playbook site.yml

Running Tag Subsets Can Leave Tasks in an Inconsistent State

Tags skip tasks — and skipped tasks do not verify their prerequisites were met. If you run --tags deploy without running --tags packages first, and the deployment depends on a package that was never installed, you will get a confusing failure mid-deploy. Always use --list-tasks to preview what will run before applying a tag subset to production. For first-time provisioning, run the full playbook without any tag filters.

Key Takeaways

Tags use OR logic — a task with multiple tags runs when any one of its tags is selected, not when all of them are. Combine --tags and --skip-tags to narrow selections further.
Tags cascade downward — a tag on a play applies to all its tasks; a tag on a block applies to all tasks inside it. Tasks inherit tags from all containing levels plus their own.
Tag handlers with the same tags as the tasks that notify them — an untagged handler is skipped when running a tag subset, leaving its service in a stale state even though its config was updated.
Use the never tag for dangerous or debug tasks — tasks tagged never are skipped in all normal runs and only execute when explicitly requested. The right way to embed destructive tasks safely in a playbook.
Always use --list-tasks before running a tag subset in production — preview exactly which tasks will execute and confirm no critical prerequisites will be skipped.

Teacher's Note

Go back to the Lesson 13 playbook, add packages, config, and services tags to the relevant tasks, then run ansible-playbook webserver.yml --tags config --list-tasks. Seeing only the config tasks listed is the moment tags stop being a concept and start being a tool you will use every day.

Practice Questions

1. What command-line flag shows all tags defined in a playbook without executing any tasks?



2. Which special built-in tag causes a task to be skipped on every normal run and only execute when that tag is explicitly requested?



3. Which command-line flag runs everything in the playbook EXCEPT the tasks carrying a specified tag?



Quiz

1. A task is tagged with both config and nginx. You run ansible-playbook site.yml --tags config. Does the task run?


2. You run --tags config. A config task changes and notifies a handler. The handler has no tags. What happens?


3. You want to add a debug task that dumps all variables to the terminal for troubleshooting, but you never want it to run in normal playbook executions. What is the correct tag strategy?


Up Next · Lesson 23

Roles Introduction

Discover Ansible roles — the standard way to package, reuse, and share automation across projects and teams, with a defined directory structure that makes every role immediately understandable.