Ansible skip_tags — Exclude Tasks When Running Playbooks

Introduction

--skip-tags lets you exclude tagged tasks from a playbook run without modifying the playbook. Skip slow tests in dev, bypass database tasks during frontend deploys, or exclude destructive operations in check mode. Combined with --tags, it gives fine-grained control over what executes.

Quick Reference

# Skip tasks tagged "slow"
ansible-playbook site.yml --skip-tags slow

# Skip multiple tags
ansible-playbook site.yml --skip-tags "tests,monitoring,backup"

# Run only "deploy" but skip "notifications"
ansible-playbook site.yml --tags deploy --skip-tags notifications

Basic Usage

---
- name: Full deployment
  hosts: webservers
  tasks:
    - name: Install packages
      ansible.builtin.apt:
        name: nginx
        state: present
      tags: install

    - name: Deploy application
      ansible.builtin.copy:
        src: app.tar.gz
        dest: /opt/app/
      tags: deploy

    - name: Run database migration
      ansible.builtin.command: /opt/app/migrate.sh
      tags: [deploy, database]

    - name: Run integration tests
      ansible.builtin.command: /opt/app/test.sh
      tags: tests

    - name: Send Slack notification
      community.general.slack:
        token: "{{ slack_token }}"
        msg: "Deployed to {{ inventory_hostname }}"
      tags: notifications

    - name: Create backup
      ansible.builtin.command: /opt/scripts/backup.sh
      tags: backup
# Deploy without running tests or notifications
ansible-playbook site.yml --skip-tags "tests,notifications"

# Everything except database migration
ansible-playbook site.yml --skip-tags database

# Only deploy, skip backup
ansible-playbook site.yml --tags deploy --skip-tags backup

Tag Inheritance

---
- name: Web deployment
  hosts: webservers
  tags: web  # All tasks inherit this tag
  tasks:
    - name: Task 1
      ansible.builtin.debug:
        msg: "This has tag: web"

    - name: Task 2
      ansible.builtin.debug:
        msg: "This has tags: web, extra"
      tags: extra

# Block-level tags
    - block:
        - name: Block task 1
          ansible.builtin.debug:
            msg: "Has: web, database"
        - name: Block task 2
          ansible.builtin.debug:
            msg: "Has: web, database"
      tags: database
# Skip entire play
ansible-playbook site.yml --skip-tags web

# Skip just the database block
ansible-playbook site.yml --skip-tags database

Special Tags

- name: Always runs (cannot be skipped with --skip-tags)
  ansible.builtin.debug:
    msg: "I always run"
  tags: always

- name: Never runs unless explicitly --tags never
  ansible.builtin.debug:
    msg: "I never run by default"
  tags: never
# "always" tag runs even when skipped... unless explicitly skipped:
ansible-playbook site.yml --skip-tags always  # This DOES skip "always" tasks

# "never" tasks only run with explicit --tags
ansible-playbook site.yml --tags never,deploy

Role Tags

---
- name: Full stack
  hosts: all
  roles:
    - role: common
      tags: common
    - role: nginx
      tags: [web, nginx]
    - role: postgresql
      tags: [database, postgresql]
    - role: monitoring
      tags: monitoring
# Deploy without monitoring setup
ansible-playbook site.yml --skip-tags monitoring

# Skip database role entirely
ansible-playbook site.yml --skip-tags database

Common Patterns

Development vs Production

- name: Run security hardening
  ansible.builtin.include_role:
    name: hardening
  tags: [security, production_only]

- name: Install debug tools
  ansible.builtin.apt:
    name: [strace, tcpdump, htop]
    state: present
  tags: [debug, dev_only]
# Production: skip dev tools
ansible-playbook site.yml --skip-tags dev_only

# Development: skip hardening
ansible-playbook site.yml --skip-tags production_only

Destructive Operations

- name: Drop and recreate database
  community.postgresql.postgresql_db:
    name: myapp
    state: absent
  tags: [database, destructive]

- name: Wipe old deployments
  ansible.builtin.file:
    path: /opt/old-releases
    state: absent
  tags: [cleanup, destructive]
# Safe run — skip anything destructive
ansible-playbook site.yml --skip-tags destructive

CI/CD Pipeline

# CI: Fast deploy, skip slow operations
ansible-playbook site.yml --skip-tags "tests,backup,monitoring"

# Nightly: Full run with tests
ansible-playbook site.yml

# Hotfix: Only deploy, skip everything else
ansible-playbook site.yml --tags deploy --skip-tags "tests,notifications"

List Available Tags

# See all tags in a playbook
ansible-playbook site.yml --list-tags

# Output:
# playbook: site.yml
#   play #1 (webservers): Full deployment  TAGS: []
#     TASK TAGS: [backup, database, deploy, install, monitoring, notifications, tests]

ansible.cfg Default Skip Tags

# ansible.cfg — always skip these tags
[tags]
skip = slow,experimental

Troubleshooting

IssueSolution
Task runs despite skip-tagsCheck if task has tags: always
Entire play skippedPlay-level tag matches skip-tags
Handler not triggeredHandler's tag is skipped, or notifying task was skipped
--skip-tags and --tags conflict--skip-tags wins — if a task matches both, it's skipped

Best Practices

  1. Tag by function — install, deploy, configure, test, backup
  2. Tag destructive tasks — easy to skip in dry runs
  3. Use always sparingly — only for truly mandatory tasks (facts gathering)
  4. Document your tags — teammates need to know what to skip
  5. Don't over-tag — every task doesn't need 5 tags
  6. CI/CD profiles — define standard skip-tag sets for different pipeline stages

Conclusion

Use --skip-tags to exclude tasks without editing playbooks. Tag by function (deploy, test, backup, destructive) and skip what you don't need per run. Combine with --tags for precise control. In CI/CD, define tag profiles for fast deploys vs full runs. Document available tags with --list-tags.