Tags
Tags label tasks, blocks, and roles so you can run or skip parts of a large playbook without splitting it into separate files. --tags deploy runs only the tagged tasks; --skip-tags config runs everything except them — the difference between re-running a full 60-task playbook and touching just the release swap.
Two special tags, always and never, override the selection entirely. The one thing tags do not do is reorder anything — they select which tasks run, never the sequence, and treating them as a dependency mechanism is the fastest way to ship a half-applied host.
Tagging Tasks, Blocks, and Roles
tags: [deploy] on a task marks that one task. A tag on a block applies to every task inside it, so you tag a logical group once. A tag on a role — or on a role entry under roles: — applies to the whole role, which is how you run "just the config role" of a multi-role play without naming every task inside it. The same tag can sit on tasks across different files, and --tags gathers all of them.
--tags and --skip-tags
--tags runs only the matching tasks and skips everything else; --skip-tags runs all but the matching ones. Passing neither runs the whole playbook as normal. The two compose for fine selection — --tags deploy --skip-tags slow runs the deploy tasks except the slow ones. The mental model is a filter over the flat task list, applied before execution begins.
always and never
A task tagged always runs on every invocation regardless of --tags — the right home for fact gathering or a safety precheck that later tasks depend on. A task tagged never runs only when you explicitly name it with --tags, which is how you hide a destructive or rarely-needed task behind an opt-in so a normal run never touches it. These two override the ordinary selection: always ignores filters, never requires being named.
Carving Larkspur into Phases
Tag the package-install and template tasks config, and the release-swap and restart tasks deploy. Now --tags deploy ships a new build without re-running the whole config pass, and --tags config pushes a settings change without triggering a deploy. One playbook, two operations, selected at the command line — instead of two playbooks that drift apart.
- name: Install the web-tier packages ansible.builtin.apt: { name: "{{ web_packages }}", state: present } tags: [config] - name: Gather facts the deploy step relies on ansible.builtin.setup: tags: [always] - name: Swap to the new release and restart gunicorn ansible.builtin.command: /usr/local/bin/larkspur-release {{ new_release }} tags: [deploy]
The fact-gathering task carries always because both a --tags config run and a --tags deploy run need those facts; tag it config instead and a deploy-only run would reference facts that were never gathered.
Listing and Verifying
--list-tags shows every tag defined in the play, and --list-tasks shows which tasks a given invocation would actually run — the check that confirms a --tags selection hits exactly what you intend before anything executes. On a 60-task playbook against production, running --list-tasks first is the cheap insurance that a tag selection does not silently skip a prerequisite or pull in something you forgot was tagged.
- Tagging only some tasks of a logical unit, so
--tags deployswaps the release but skips the untagged handler that restarts it, leaving old code running under a new symlink. - Forgetting that a task with no tag is skipped under
--tags, so a--tags configrun silently omits an untagged prerequisite and the config applies against a half-set-up host. - Tagging fact gathering or a guard with a normal tag instead of
always, so a--tags deployrun skips it and later tasks reference facts that were never gathered. - Relying on tags to enforce ordering — tags select which tasks run, not the order; an untagged dependency that
--tagsskips will not magically run first. - Leaving a destructive task on a normal tag instead of
never, so a broad--tagsselection sweeps it in and runs the dangerous step nobody meant to trigger.
- Tag complete logical units — every task plus its handler — so a
--tags deployrun is self-contained and never half a phase. - Tag fact gathering and safety checks
alwaysso they survive any--tagsselection that later tasks depend on. - Hide destructive or rare tasks behind
neverso they run only when explicitly requested by name. - Confirm a selection with
--list-tasksbefore running--tags/--skip-tagsagainst anything that matters, so you see exactly what executes. - Keep a small, consistent tag vocabulary —
config,deploy,packages— across roles, so the same tag means the same phase everywhere.
-target selects resources by address — a graph operation, not a label
Make targets select by name, not by tag
No direct equivalent — tags are an Ansible-internal selection mechanism
Knowledge Check
You run a playbook with --tags config. What happens to a task that has no tag at all?
- It is skipped — under
--tags, only tasks carrying a matching tag run, so an untagged prerequisite is silently omitted - It runs anyway, because every untagged task is treated as if it were implicitly tagged with each and every tag that exists
- The play aborts with an error, flatly refusing to run until every single task in it has been given a tag
- It runs first, because untagged tasks are automatically prioritized and moved ahead of any tagged ones under
--tags
What is the difference between the always and never tags?
alwaysruns on every invocation regardless of--tags;neverruns only when explicitly named with--tagsalwaysforces a task to run first andneverforces it to run last, controlling execution order rather than selectionneveris the tag that runs on every single invocation, whilealwaysis the one that must be explicitly requested by name- Both are interchangeable aliases that simply hide a task from the output of
--list-tags
A teammate expects --tags to make a tagged dependency run before an untagged one it relies on. Why is that wrong?
- Tags select which tasks run, not the order — and the untagged dependency is skipped entirely, not reordered
- Tags actively reverse the entire play order, so the tagged dependency would end up running dead last instead of first
- Tags only ever affect whole roles and never individual tasks, so the ordering of the dependency is left completely unaffected
- The dependency quietly runs twice under
--tags, which masks the underlying ordering problem from view
What does --list-tasks give you before a tagged run against production?
- A preview of exactly which tasks the current
--tagsselection would execute, so you can confirm it hits what you intend - A dry run that actually applies all the selected tasks on the host and then cleanly rolls every one of them back afterward
- A complete list of every host the play would touch, neatly grouped under each tag it matches
- A line-by-line diff of the exact changes each tagged task would make on disk before they happen
You got correct