Ansible Course
Tags and Selective Execution
In this lesson
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.
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.
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.
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.
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.
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
packages, config, services,
users, security, deploy. These map to
the natural groups of tasks in any playbook.
nginx, postgres,
docker. This allows targeting all nginx-related tasks (config,
service, packages) in one command.
nginx:config or security/ssh.
Use multiple simple tags per task instead:
tags: [nginx, config].
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.
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 and --skip-tags to narrow selections
further.
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.
--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.