Ansible Tags

Last updated: July 1, 2025

My Journey with Selective Automation

I'll never forget the day when one of my colleagues asked me to "just update the web server configuration" on our production environment. I confidently ran my comprehensive Ansible playbook that managed everything from database setup to monitoring configuration. Twenty minutes later, I realized I had just rebooted all database servers, updated SSL certificates, and restarted critical services – all because I wanted to change a single nginx configuration file.

That's when I discovered the power of Ansible tags. What should have been a 2-minute configuration update turned into a 30-minute emergency response. If I had properly tagged my tasks, I could have run only the web server tasks and avoided the unnecessary downtime.

This experience taught me that writing great automation isn't just about making tasks work – it's about making them work selectively and efficiently. Ansible tags provide the surgical precision needed to execute exactly what you want, when you want it, without affecting unrelated components.

In this post, I'll share how to master Ansible tags across Linux and Windows environments, making your automation both powerful and precise.

Understanding Ansible Tags

Ansible tags are labels that you can assign to tasks, plays, roles, blocks, or imports in your playbooks. Think of them as metadata that allows you to categorize and selectively execute parts of your automation based on what you actually need to accomplish.

Here's what makes tags powerful:

  1. Selective execution - Run only the tasks you need

  2. Skip unnecessary steps - Avoid time-consuming operations when not needed

  3. Environment-specific tasks - Target different environments with precision

  4. Debugging and testing - Isolate specific functionality for troubleshooting

  5. Efficiency - Reduce execution time by focusing on relevant tasks

Basic Tagging Syntax

The fundamental syntax for adding tags is straightforward – use the tags: keyword followed by your tag names:

Single Tag

---
- name: Install web server package
  ansible.builtin.yum:
    name: httpd
    state: present
  tags: webserver

Multiple Tags

---
- name: Configure firewall for web server
  ansible.posix.firewalld:
    service: http
    permanent: true
    state: enabled
  tags:
    - webserver
    - security
    - firewall

Tagging at Different Levels

Task-Level Tagging

The most granular level of tagging is at the individual task level:

---
- name: Install packages for web server
  ansible.builtin.yum:
    name: "{{ item }}"
    state: present
  loop:
    - httpd
    - mod_ssl
  tags:
    - packages
    - webserver

- name: Configure web server
  ansible.builtin.template:
    src: httpd.conf.j2
    dest: /etc/httpd/conf/httpd.conf
  tags:
    - configuration
    - webserver
  notify: restart httpd

- name: Start web server service
  ansible.builtin.systemd:
    name: httpd
    state: started
    enabled: yes
  tags:
    - services
    - webserver

Block-Level Tagging

When you want to apply the same tags to multiple related tasks, use blocks:

---
- name: Web server setup
  block:
    - name: Install web server packages
      ansible.builtin.yum:
        name:
          - httpd
          - mod_ssl
          - mod_rewrite
        state: present

    - name: Create web directories
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        owner: apache
        group: apache
        mode: '0755'
      loop:
        - /var/www/html/app
        - /var/www/logs

    - name: Configure virtual hosts
      ansible.builtin.template:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
      loop:
        - { src: vhost.conf.j2, dest: /etc/httpd/conf.d/app.conf }
        - { src: ssl.conf.j2, dest: /etc/httpd/conf.d/ssl.conf }
  tags:
    - webserver
    - apache

- name: Database setup
  block:
    - name: Install MySQL
      ansible.builtin.yum:
        name: mysql-server
        state: present

    - name: Configure MySQL
      ansible.builtin.template:
        src: my.cnf.j2
        dest: /etc/my.cnf
  tags:
    - database
    - mysql

Play-Level Tagging

You can tag entire plays to categorize major sections of your automation:

---
- name: Web server deployment
  hosts: webservers
  tags: 
    - webserver
    - frontend
  tasks:
    - name: Install Apache
      ansible.builtin.yum:
        name: httpd
        state: present

    - name: Start Apache
      ansible.builtin.systemd:
        name: httpd
        state: started

- name: Database deployment
  hosts: databases
  tags:
    - database
    - backend
  tasks:
    - name: Install MySQL
      ansible.builtin.yum:
        name: mysql-server
        state: present

    - name: Start MySQL
      ansible.builtin.systemd:
        name: mysqld
        state: started

Role-Level Tagging

When using roles, you can tag them in the playbook:

---
- name: Complete application deployment
  hosts: all
  roles:
    - role: common
      tags:
        - base
        - security
    
    - role: webserver
      tags:
        - webserver
        - apache
    
    - role: database
      tags:
        - database
        - mysql
    
    - role: monitoring
      tags:
        - monitoring
        - observability

You can also define tags within the role's tasks:

# roles/webserver/tasks/main.yml
---
- name: Install web server for RHEL family
  include_tasks: redhat.yml
  when: ansible_facts['os_family'] | lower == 'redhat'
  tags:
    - packages
    - redhat

- name: Install web server for Debian family
  include_tasks: debian.yml
  when: ansible_facts['os_family'] | lower == 'debian'
  tags:
    - packages
    - debian

- name: Configure web server
  include_tasks: configure.yml
  tags:
    - configuration

Cross-Platform Examples: Linux and Windows

Linux Web Server Setup

---
- name: Linux web server configuration
  hosts: linux_servers
  tasks:
    - name: Install Apache on RHEL/CentOS
      ansible.builtin.yum:
        name: httpd
        state: present
      when: ansible_facts['os_family'] == 'RedHat'
      tags:
        - packages
        - webserver
        - linux

    - name: Install Apache on Ubuntu/Debian
      ansible.builtin.apt:
        name: apache2
        state: present
      when: ansible_facts['os_family'] == 'Debian'
      tags:
        - packages
        - webserver
        - linux

    - name: Configure firewall for web traffic
      ansible.posix.firewalld:
        service: http
        permanent: true
        state: enabled
      tags:
        - security
        - firewall
        - linux

    - name: Start and enable web server
      ansible.builtin.systemd:
        name: "{{ 'httpd' if ansible_facts['os_family'] == 'RedHat' else 'apache2' }}"
        state: started
        enabled: yes
      tags:
        - services
        - webserver
        - linux

Windows IIS Setup

---
- name: Windows IIS configuration
  hosts: windows_servers
  tasks:
    - name: Install IIS feature
      ansible.windows.win_feature:
        name: IIS-WebServerRole
        state: present
      tags:
        - packages
        - webserver
        - windows

    - name: Install IIS management tools
      ansible.windows.win_feature:
        name: IIS-ManagementConsole
        state: present
      tags:
        - packages
        - management
        - windows

    - name: Configure Windows Firewall for HTTP
      ansible.windows.win_firewall_rule:
        name: "HTTP Inbound"
        localport: 80
        action: allow
        direction: in
        protocol: tcp
        state: present
      tags:
        - security
        - firewall
        - windows

    - name: Start IIS service
      ansible.windows.win_service:
        name: W3SVC
        state: started
        start_mode: auto
      tags:
        - services
        - webserver
        - windows

Cross-Platform Application Deployment

---
- name: Deploy application across platforms
  hosts: all
  tasks:
    # Common tasks
    - name: Create application directory (Linux)
      ansible.builtin.file:
        path: /opt/myapp
        state: directory
        mode: '0755'
      when: ansible_facts['os_family'] != 'Windows'
      tags:
        - deployment
        - directories
        - linux

    - name: Create application directory (Windows)
      ansible.windows.win_file:
        path: C:\MyApp
        state: directory
      when: ansible_facts['os_family'] == 'Windows'
      tags:
        - deployment
        - directories
        - windows

    # Configuration files
    - name: Deploy configuration (Linux)
      ansible.builtin.template:
        src: app.conf.j2
        dest: /opt/myapp/app.conf
      when: ansible_facts['os_family'] != 'Windows'
      tags:
        - configuration
        - deployment
        - linux

    - name: Deploy configuration (Windows)
      ansible.windows.win_template:
        src: app.conf.j2
        dest: C:\MyApp\app.conf
      when: ansible_facts['os_family'] == 'Windows'
      tags:
        - configuration
        - deployment
        - windows

    # Service management
    - name: Install and start service (Linux)
      block:
        - name: Copy service file
          ansible.builtin.template:
            src: myapp.service.j2
            dest: /etc/systemd/system/myapp.service

        - name: Start service
          ansible.builtin.systemd:
            name: myapp
            state: started
            enabled: yes
            daemon_reload: yes
      when: ansible_facts['os_family'] != 'Windows'
      tags:
        - services
        - deployment
        - linux

    - name: Install and start service (Windows)
      ansible.windows.win_service:
        name: MyAppService
        path: C:\MyApp\MyAppService.exe
        state: started
        start_mode: auto
      when: ansible_facts['os_family'] == 'Windows'
      tags:
        - services
        - deployment
        - windows

Special Tags: Always and Never

Ansible provides two special tags with unique behaviors:

The always Tag

Tasks tagged with always run by default, regardless of what tags you specify:

---
- name: System preparation tasks
  hosts: all
  tasks:
    - name: Update package cache
      ansible.builtin.package:
        update_cache: yes
      tags: always

    - name: Check disk space
      ansible.builtin.shell: df -h
      register: disk_usage
      tags: always

    - name: Display disk usage
      ansible.builtin.debug:
        var: disk_usage.stdout_lines
      tags: always

    - name: Install web server
      ansible.builtin.yum:
        name: httpd
        state: present
      tags: webserver

    - name: Install database
      ansible.builtin.yum:
        name: mysql-server
        state: present
      tags: database

The never Tag

Tasks tagged with never are skipped by default and only run when explicitly called:

---
- name: System maintenance tasks
  hosts: all
  tasks:
    - name: Regular system update
      ansible.builtin.yum:
        name: "*"
        state: latest
      tags: updates

    - name: Clean package cache
      ansible.builtin.shell: yum clean all
      tags: cleanup

    - name: Reboot system (dangerous!)
      ansible.builtin.reboot:
        reboot_timeout: 300
      tags:
        - never
        - reboot
        - dangerous

    - name: Factory reset configuration
      ansible.builtin.shell: rm -rf /etc/myapp/*
      tags:
        - never
        - factory-reset
        - dangerous

Running Playbooks with Tags

Basic Tag Execution

# Run only tasks tagged with 'webserver'
ansible-playbook site.yml --tags "webserver"

# Run multiple tags
ansible-playbook site.yml --tags "webserver,database"

# Skip specific tags
ansible-playbook site.yml --skip-tags "dangerous,reboot"

# Run tagged tasks
ansible-playbook site.yml --tags "tagged"

# Run untagged tasks
ansible-playbook site.yml --tags "untagged"

# Run everything (default behavior)
ansible-playbook site.yml --tags "all"

Advanced Tag Operations

# List all available tags
ansible-playbook site.yml --list-tags

# Preview which tasks would run with specific tags
ansible-playbook site.yml --tags "webserver" --list-tasks

# Run specific tags while skipping others
ansible-playbook site.yml --tags "deployment,configuration" --skip-tags "dangerous"

# Force running 'never' tagged tasks
ansible-playbook site.yml --tags "never"

# Run everything including 'never' tagged tasks
ansible-playbook site.yml --tags "all,never"

Real-World Tag Execution Examples

# Deploy only web components
ansible-playbook infrastructure.yml --tags "webserver,loadbalancer"

# Update only configuration files
ansible-playbook infrastructure.yml --tags "configuration"

# Security-only updates
ansible-playbook infrastructure.yml --tags "security,firewall,certificates"

# Platform-specific deployment
ansible-playbook cross-platform.yml --tags "linux"
ansible-playbook cross-platform.yml --tags "windows"

# Emergency maintenance (skip time-consuming tasks)
ansible-playbook maintenance.yml --skip-tags "backup,updates"

Tag Inheritance and Best Practices

Understanding Tag Inheritance

When you apply tags at higher levels (plays, roles, imports), they automatically inherit to all contained tasks:

---
# Playbook: infrastructure.yml
- name: Database server setup
  hosts: database_servers
  tags:
    - database
    - backend
  roles:
    - role: mysql
      tags:
        - mysql
    - role: postgresql  
      tags:
        - postgresql

# All tasks in both roles inherit 'database' and 'backend' tags
# MySQL role tasks also get 'mysql' tag
# PostgreSQL role tasks also get 'postgresql' tag

Tag Naming Conventions

Establish consistent naming patterns for your organization:

# Environment-based tags
tags: 
  - prod
  - staging
  - dev

# Component-based tags
tags:
  - webserver
  - database
  - loadbalancer
  - monitoring

# Function-based tags
tags:
  - deployment
  - configuration
  - backup
  - security

# Platform-based tags
tags:
  - linux
  - windows
  - centos
  - ubuntu

# Layer-based tags
tags:
  - infrastructure
  - application
  - middleware
  - frontend
  - backend

Comprehensive Tagging Strategy

---
- name: Complete application stack
  hosts: all
  tasks:
    # Base system preparation (always runs)
    - name: Update system packages
      ansible.builtin.package:
        name: "*"
        state: latest
      tags: 
        - always
        - system
        - updates
      when: allow_system_updates | default(false)

    # Security configuration
    - name: Configure SSH security
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
      loop:
        - { regexp: '^PermitRootLogin', line: 'PermitRootLogin no' }
        - { regexp: '^PasswordAuthentication', line: 'PasswordAuthentication no' }
      tags:
        - security
        - ssh
        - hardening
        - infrastructure

    # Web server setup
    - name: Install and configure web server
      block:
        - name: Install web server packages
          ansible.builtin.package:
            name: "{{ web_packages }}"
            state: present

        - name: Configure web server
          ansible.builtin.template:
            src: "{{ web_config_template }}"
            dest: "{{ web_config_path }}"
          notify: restart webserver

        - name: Start web server
          ansible.builtin.service:
            name: "{{ web_service_name }}"
            state: started
            enabled: yes
      tags:
        - webserver
        - apache
        - frontend
        - application

    # Database setup
    - name: Install and configure database
      block:
        - name: Install database packages
          ansible.builtin.package:
            name: "{{ db_packages }}"
            state: present

        - name: Configure database
          ansible.builtin.template:
            src: "{{ db_config_template }}"
            dest: "{{ db_config_path }}"
          notify: restart database

        - name: Initialize database
          ansible.builtin.shell: "{{ db_init_command }}"
          args:
            creates: "{{ db_data_dir }}/initialized"
      tags:
        - database
        - mysql
        - backend
        - application

    # Monitoring setup
    - name: Deploy monitoring agents
      block:
        - name: Install monitoring agent
          ansible.builtin.package:
            name: "{{ monitoring_package }}"
            state: present

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

        - name: Start monitoring service
          ansible.builtin.service:
            name: "{{ monitoring_service }}"
            state: started
            enabled: yes
      tags:
        - monitoring
        - observability
        - infrastructure

    # Maintenance tasks (never runs by default)
    - name: Perform maintenance tasks
      block:
        - name: Clear application logs
          ansible.builtin.shell: find /var/log -name "*.log" -mtime +30 -delete

        - name: Vacuum database
          ansible.builtin.shell: "{{ db_vacuum_command }}"

        - name: Restart all services
          ansible.builtin.service:
            name: "{{ item }}"
            state: restarted
          loop:
            - "{{ web_service_name }}"
            - "{{ db_service_name }}"
            - "{{ monitoring_service }}"
      tags:
        - never
        - maintenance
        - cleanup

Debugging and Troubleshooting with Tags

Using Tags for Development

---
- name: Development and debugging tasks
  hosts: dev_servers
  tasks:
    - name: Deploy latest code
      ansible.builtin.git:
        repo: "{{ app_repo }}"
        dest: "{{ app_path }}"
        version: "{{ app_branch | default('main') }}"
      tags:
        - deployment
        - code
        - dev

    - name: Install development dependencies
      ansible.builtin.pip:
        requirements: "{{ app_path }}/requirements-dev.txt"
        virtualenv: "{{ app_path }}/venv"
      tags:
        - dependencies
        - dev
        - python

    - name: Enable debug logging
      ansible.builtin.lineinfile:
        path: "{{ app_path }}/config.ini"
        regexp: '^log_level'
        line: 'log_level = DEBUG'
      tags:
        - debug
        - logging
        - dev

    - name: Start application in development mode
      ansible.builtin.command:
        cmd: "{{ app_path }}/venv/bin/python {{ app_path }}/app.py --dev"
      async: 3600
      poll: 0
      tags:
        - dev
        - start

Testing Specific Components

# Test only web server configuration
ansible-playbook site.yml --tags "webserver" --check --diff

# Deploy only to development environment
ansible-playbook site.yml --tags "dev" --limit "dev_servers"

# Run configuration tasks without making changes
ansible-playbook site.yml --tags "configuration" --check

# Test database deployment
ansible-playbook site.yml --tags "database" --list-tasks

Advanced Tag Patterns

Environment-Specific Tagging

---
# inventory/group_vars/production.yml
env_tags:
  - prod
  - production

# inventory/group_vars/staging.yml  
env_tags:
  - staging
  - test

# inventory/group_vars/development.yml
env_tags:
  - dev
  - development

# playbooks/deploy.yml
- name: Environment-aware deployment
  hosts: all
  tasks:
    - name: Deploy production configuration
      ansible.builtin.template:
        src: app-prod.conf.j2
        dest: /etc/app/app.conf
      tags: "{{ env_tags }}"
      when: "'prod' in env_tags"

    - name: Deploy development configuration
      ansible.builtin.template:
        src: app-dev.conf.j2
        dest: /etc/app/app.conf
      tags: "{{ env_tags }}"
      when: "'dev' in env_tags"

    - name: Install debug tools
      ansible.builtin.package:
        name: "{{ debug_packages }}"
        state: present
      tags:
        - debug
        - dev
      when: "'dev' in env_tags"

Conditional Tag Application

---
- name: Conditional tag examples
  hosts: all
  vars:
    deployment_type: rolling
    enable_monitoring: true
  tasks:
    - name: Standard deployment
      ansible.builtin.template:
        src: app.conf.j2
        dest: /etc/app.conf
      tags:
        - deployment
        - "{{ 'standard' if deployment_type == 'standard' else 'rolling' }}"

    - name: Enable monitoring
      ansible.builtin.service:
        name: monitoring-agent
        state: started
        enabled: yes
      tags:
        - monitoring
        - "{{ 'monitoring-enabled' if enable_monitoring else 'monitoring-disabled' }}"

Performance and Optimization

Tag-Based Performance Optimization

---
- name: Performance-optimized playbook
  hosts: all
  tasks:
    # Quick tasks (always run for validation)
    - name: Check system status
      ansible.builtin.command: uptime
      register: system_uptime
      tags: 
        - always
        - quick
        - validation

    # Time-consuming tasks (run only when needed)
    - name: Full system update
      ansible.builtin.package:
        name: "*"
        state: latest
      tags:
        - updates
        - maintenance
        - slow

    - name: Rebuild application cache
      ansible.builtin.command: "{{ app_path }}/rebuild-cache.sh"
      tags:
        - cache
        - performance
        - slow

    # Critical deployment tasks
    - name: Deploy application code
      ansible.builtin.git:
        repo: "{{ app_repo }}"
        dest: "{{ app_path }}"
        version: "{{ app_version }}"
      tags:
        - deployment
        - critical
        - application

    - name: Restart application services
      ansible.builtin.service:
        name: "{{ item }}"
        state: restarted
      loop: "{{ app_services }}"
      tags:
        - deployment
        - critical
        - services

Execution Time Examples

# Quick validation (runs in seconds)
ansible-playbook deploy.yml --tags "quick,validation"

# Full deployment (runs in minutes)
ansible-playbook deploy.yml --tags "deployment,critical"

# Maintenance mode (runs in hours)
ansible-playbook deploy.yml --tags "maintenance,updates,slow"

# Emergency deployment (skip slow tasks)
ansible-playbook deploy.yml --tags "critical" --skip-tags "slow"

Real-World Use Cases

DevOps Pipeline Integration

---
# ci-cd-pipeline.yml
- name: CI/CD Pipeline with Tags
  hosts: "{{ target_environment }}"
  tasks:
    # Build phase
    - name: Checkout source code
      ansible.builtin.git:
        repo: "{{ source_repo }}"
        dest: "{{ build_path }}"
        version: "{{ git_commit }}"
      tags:
        - build
        - checkout
        - ci

    - name: Run tests
      ansible.builtin.command:
        cmd: make test
        chdir: "{{ build_path }}"
      tags:
        - build
        - test
        - ci

    - name: Build application
      ansible.builtin.command:
        cmd: make build
        chdir: "{{ build_path }}"
      tags:
        - build
        - compile
        - ci

    # Deploy phase
    - name: Stop application services
      ansible.builtin.service:
        name: "{{ item }}"
        state: stopped
      loop: "{{ app_services }}"
      tags:
        - deploy
        - stop
        - cd

    - name: Deploy new version
      ansible.builtin.copy:
        src: "{{ build_path }}/dist/"
        dest: "{{ app_deploy_path }}"
        remote_src: yes
      tags:
        - deploy
        - copy
        - cd

    - name: Update configuration
      ansible.builtin.template:
        src: "{{ app_config_template }}"
        dest: "{{ app_config_path }}"
      tags:
        - deploy
        - config
        - cd

    - name: Start application services
      ansible.builtin.service:
        name: "{{ item }}"
        state: started
      loop: "{{ app_services }}"
      tags:
        - deploy
        - start
        - cd

    # Verification phase
    - name: Health check
      ansible.builtin.uri:
        url: "{{ app_health_url }}"
        method: GET
        status_code: 200
      retries: 5
      delay: 10
      tags:
        - verify
        - health
        - cd

    - name: Smoke tests
      ansible.builtin.command:
        cmd: "{{ smoke_test_command }}"
      tags:
        - verify
        - smoke
        - cd

Pipeline execution examples:

# Full CI/CD pipeline
ansible-playbook ci-cd-pipeline.yml

# Build only
ansible-playbook ci-cd-pipeline.yml --tags "ci"

# Deploy only (skip build)
ansible-playbook ci-cd-pipeline.yml --tags "cd"

# Deploy and verify
ansible-playbook ci-cd-pipeline.yml --tags "deploy,verify"

# Quick verification
ansible-playbook ci-cd-pipeline.yml --tags "health"

Multi-Environment Management

---
# multi-env-deploy.yml
- name: Multi-environment deployment
  hosts: "{{ env }}_servers"
  vars:
    common_tags:
      - "{{ env }}"
      - deployment
  tasks:
    - name: Deploy to development
      block:
        - name: Use development database
          ansible.builtin.template:
            src: db-dev.conf.j2
            dest: /etc/app/db.conf

        - name: Enable debug mode
          ansible.builtin.lineinfile:
            path: /etc/app/app.conf
            line: "DEBUG=true"

        - name: Install development tools
          ansible.builtin.package:
            name: "{{ dev_tools }}"
            state: present
      tags:
        - dev
        - development
        - debug

    - name: Deploy to staging
      block:
        - name: Use staging database
          ansible.builtin.template:
            src: db-staging.conf.j2
            dest: /etc/app/db.conf

        - name: Configure load testing
          ansible.builtin.template:
            src: loadtest.conf.j2
            dest: /etc/app/loadtest.conf

        - name: Enable performance monitoring
          ansible.builtin.service:
            name: performance-monitor
            state: started
      tags:
        - staging
        - test
        - performance

    - name: Deploy to production
      block:
        - name: Use production database
          ansible.builtin.template:
            src: db-prod.conf.j2
            dest: /etc/app/db.conf

        - name: Configure high availability
          ansible.builtin.template:
            src: ha.conf.j2
            dest: /etc/app/ha.conf

        - name: Enable production monitoring
          ansible.builtin.service:
            name: prod-monitor
            state: started

        - name: Configure backup
          ansible.builtin.cron:
            name: "Application backup"
            minute: "0"
            hour: "2"
            job: "/usr/local/bin/backup-app.sh"
      tags:
        - prod
        - production
        - backup
        - ha

Environment-specific execution:

# Deploy to development
ansible-playbook multi-env-deploy.yml -e "env=dev" --tags "dev"

# Deploy to staging
ansible-playbook multi-env-deploy.yml -e "env=staging" --tags "staging"

# Production deployment
ansible-playbook multi-env-deploy.yml -e "env=prod" --tags "prod"

# Configure HA across all production servers
ansible-playbook multi-env-deploy.yml -e "env=prod" --tags "ha"

Common Pitfalls and Solutions

1. Over-tagging

Problem: Adding too many tags makes execution confusing:

# DON'T: Too many tags
- name: Install package
  ansible.builtin.yum:
    name: httpd
    state: present
  tags:
    - packages
    - install
    - httpd
    - webserver
    - apache
    - web
    - server
    - rpm
    - yum

Solution: Use meaningful, distinct tags:

# DO: Clear, purposeful tags
- name: Install package
  ansible.builtin.yum:
    name: httpd
    state: present
  tags:
    - packages
    - webserver

2. Inconsistent Tag Naming

Problem: Mixed naming conventions:

# DON'T: Inconsistent naming
tags:
  - web_server    # snake_case
  - database      # lowercase
  - Load-Balancer # kebab-case with capitals
  - MONITORING    # UPPERCASE

Solution: Establish and follow conventions:

# DO: Consistent naming
tags:
  - webserver     # lowercase
  - database      # lowercase  
  - loadbalancer  # lowercase
  - monitoring    # lowercase

3. Missing Tag Dependencies

Problem: Tasks have dependencies but different tags:

# DON'T: Dependent tasks with different tags
- name: Install database
  ansible.builtin.yum:
    name: mysql-server
    state: present
  tags: packages

- name: Start database
  ansible.builtin.service:
    name: mysqld
    state: started
  tags: services  # Different tag!

Solution: Use consistent tags for related tasks:

# DO: Consistent tags for dependent tasks
- name: Install database
  ansible.builtin.yum:
    name: mysql-server
    state: present
  tags:
    - database
    - packages

- name: Start database
  ansible.builtin.service:
    name: mysqld
    state: started
  tags:
    - database
    - services

Conclusion

Ansible tags transformed my automation from a blunt instrument into a precision tool. What started as a painful lesson about running unnecessary tasks in production became the foundation for building efficient, maintainable automation.

The key insights I've learned:

  1. Start simple - Begin with basic component tags (webserver, database, monitoring)

  2. Be consistent - Establish naming conventions and stick to them

  3. Think in layers - Use tags for infrastructure, application, and operational concerns

  4. Plan for scale - Design your tagging strategy to grow with your infrastructure

  5. Test frequently - Use --list-tasks and --check to verify tag behavior

Remember, tags aren't just about selective execution – they're about making your automation more maintainable, testable, and collaborative. When a team member needs to make a quick configuration change at 2 AM, clear tagging can be the difference between a 5-minute fix and a 2-hour outage.

Whether you're managing a small web application or a complex multi-environment infrastructure, mastering Ansible tags will make you more efficient and your automation more reliable. Start tagging your playbooks today, and experience the precision that comes with surgical automation control.

Additional Resources


Have questions about Ansible tags or want to share your own automation experiences? Feel free to reach out through the comments below. Let's build better automation together! dest: "{{ web_server_config_path }}" notify: restart web server tags: - configuration - webserver

- name: Start and enable web server
  service:
    name: "{{ web_server_service }}"
    state: started
    enabled: yes
  tags:
    - services
    - webserver

### Running Tagged Tasks

Once you've added tags, you can execute specific tagged tasks:

```bash
# Run only tasks tagged with 'packages'
ansible-playbook web_deployment.yml --tags "packages"

# Run tasks tagged with either 'configuration' or 'services'
ansible-playbook web_deployment.yml --tags "configuration,services"

# Skip tasks tagged with 'packages'
ansible-playbook web_deployment.yml --skip-tags "packages"

# List all available tags
ansible-playbook web_deployment.yml --list-tags

# Preview which tasks would run with specific tags
ansible-playbook web_deployment.yml --tags "webserver" --list-tasks

Linux Automation Examples

Example 1: LAMP Stack Deployment with Tags

Let's create a comprehensive Linux web server deployment with proper tagging:

lamp_deployment.yml

---
- name: LAMP Stack Deployment
  hosts: linux_web_servers
  become: yes
  vars:
    mysql_root_password: "SecureRootPassword123!"
    app_db_name: "webapp_db"
    app_db_user: "webapp_user"
    app_db_password: "WebAppPassword456!"
  
  tasks:
    # Package Installation Tasks
    - name: Update package cache (Debian/Ubuntu)
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_facts['os_family'] == 'Debian'
      tags:
        - packages
        - system_update

    - name: Update package cache (RHEL/CentOS)
      yum:
        update_cache: yes
      when: ansible_facts['os_family'] == 'RedHat'
      tags:
        - packages
        - system_update

    - name: Install Apache web server
      package:
        name: "{{ apache_package }}"
        state: present
      vars:
        apache_package: "{{ 'httpd' if ansible_facts['os_family'] == 'RedHat' else 'apache2' }}"
      tags:
        - packages
        - webserver
        - apache

    - name: Install MySQL/MariaDB
      package:
        name: "{{ mysql_packages }}"
        state: present
      vars:
        mysql_packages:
          - "{{ 'mariadb-server' if ansible_facts['os_family'] == 'RedHat' else 'mysql-server' }}"
          - "{{ 'python3-PyMySQL' if ansible_facts['os_family'] == 'RedHat' else 'python3-pymysql' }}"
      tags:
        - packages
        - database
        - mysql

    - name: Install PHP and extensions
      package:
        name: "{{ php_packages }}"
        state: present
      vars:
        php_packages:
          - "{{ 'php' if ansible_facts['os_family'] == 'RedHat' else 'php' }}"
          - "{{ 'php-mysql' if ansible_facts['os_family'] == 'RedHat' else 'php-mysql' }}"
          - "{{ 'php-fpm' if ansible_facts['os_family'] == 'RedHat' else 'php-fpm' }}"
      tags:
        - packages
        - php
        - webserver

    # Service Configuration Tasks
    - name: Start and enable Apache
      service:
        name: "{{ apache_service }}"
        state: started
        enabled: yes
      vars:
        apache_service: "{{ 'httpd' if ansible_facts['os_family'] == 'RedHat' else 'apache2' }}"
      tags:
        - services
        - webserver
        - apache

    - name: Start and enable MySQL
      service:
        name: "{{ mysql_service }}"
        state: started
        enabled: yes
      vars:
        mysql_service: "{{ 'mariadb' if ansible_facts['os_family'] == 'RedHat' else 'mysql' }}"
      tags:
        - services
        - database
        - mysql

    # Database Configuration Tasks
    - name: Set MySQL root password
      mysql_user:
        name: root
        password: "{{ mysql_root_password }}"
        login_user: root
        login_password: "{{ mysql_root_password }}"
        check_implicit_admin: yes
        priv: "*.*:ALL,GRANT"
        host: localhost
        state: present
      tags:
        - database
        - mysql
        - security

    - name: Create application database
      mysql_db:
        name: "{{ app_db_name }}"
        state: present
        login_user: root
        login_password: "{{ mysql_root_password }}"
      tags:
        - database
        - mysql
        - application

    - name: Create application database user
      mysql_user:
        name: "{{ app_db_user }}"
        password: "{{ app_db_password }}"
        priv: "{{ app_db_name }}.*:ALL"
        host: localhost
        state: present
        login_user: root
        login_password: "{{ mysql_root_password }}"
      tags:
        - database
        - mysql
        - application

    # Web Server Configuration Tasks
    - name: Configure Apache virtual host
      template:
        src: vhost.conf.j2
        dest: "{{ vhost_path }}"
        backup: yes
      vars:
        vhost_path: "{{ '/etc/httpd/conf.d/webapp.conf' if ansible_facts['os_family'] == 'RedHat' else '/etc/apache2/sites-available/webapp.conf' }}"
      notify: restart apache
      tags:
        - configuration
        - webserver
        - apache

    - name: Enable Apache site (Debian/Ubuntu)
      command: a2ensite webapp.conf
      when: ansible_facts['os_family'] == 'Debian'
      notify: restart apache
      tags:
        - configuration
        - webserver
        - apache

    # Application Deployment Tasks
    - name: Create web application directory
      file:
        path: /var/www/webapp
        state: directory
        owner: "{{ web_user }}"
        group: "{{ web_group }}"
        mode: '0755'
      vars:
        web_user: "{{ 'apache' if ansible_facts['os_family'] == 'RedHat' else 'www-data' }}"
        web_group: "{{ 'apache' if ansible_facts['os_family'] == 'RedHat' else 'www-data' }}"
      tags:
        - application
        - deployment

    - name: Deploy application files
      template:
        src: "{{ item.src }}"
        dest: "/var/www/webapp/{{ item.dest }}"
        owner: "{{ web_user }}"
        group: "{{ web_group }}"
        mode: '0644'
      vars:
        web_user: "{{ 'apache' if ansible_facts['os_family'] == 'RedHat' else 'www-data' }}"
        web_group: "{{ 'apache' if ansible_facts['os_family'] == 'RedHat' else 'www-data' }}"
      loop:
        - { src: 'index.php.j2', dest: 'index.php' }
        - { src: 'config.php.j2', dest: 'config.php' }
      tags:
        - application
        - deployment

    # Security Configuration Tasks
    - name: Configure firewall for HTTP/HTTPS
      firewalld:
        service: "{{ item }}"
        permanent: yes
        state: enabled
        immediate: yes
      loop:
        - http
        - https
      when: ansible_facts['os_family'] == 'RedHat'
      tags:
        - security
        - firewall

    - name: Configure UFW for HTTP/HTTPS
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - '80'
        - '443'
      when: ansible_facts['os_family'] == 'Debian'
      tags:
        - security
        - firewall

  handlers:
    - name: restart apache
      service:
        name: "{{ apache_service }}"
        state: restarted
      vars:
        apache_service: "{{ 'httpd' if ansible_facts['os_family'] == 'RedHat' else 'apache2' }}"

Example 2: Linux System Monitoring Setup

monitoring_deployment.yml

---
- name: Linux System Monitoring Setup
  hosts: linux_servers
  become: yes
  vars:
    prometheus_version: "2.40.0"
    node_exporter_version: "1.5.0"
    grafana_admin_password: "GrafanaAdmin2023!"
  
  tasks:
    # User and Directory Setup
    - name: Create monitoring users
      user:
        name: "{{ item }}"
        system: yes
        shell: /bin/false
        home: "/opt/{{ item }}"
        create_home: no
      loop:
        - prometheus
        - node_exporter
      tags:
        - setup
        - users
        - monitoring

    - name: Create monitoring directories
      file:
        path: "{{ item }}"
        state: directory
        owner: prometheus
        group: prometheus
        mode: '0755'
      loop:
        - /opt/prometheus
        - /var/lib/prometheus
        - /etc/prometheus
      tags:
        - setup
        - directories
        - monitoring

    # Prometheus Installation
    - name: Download Prometheus
      get_url:
        url: "https://github.com/prometheus/prometheus/releases/download/v{{ prometheus_version }}/prometheus-{{ prometheus_version }}.linux-amd64.tar.gz"
        dest: "/tmp/prometheus-{{ prometheus_version }}.tar.gz"
        mode: '0644'
      tags:
        - download
        - prometheus
        - monitoring

    - name: Extract Prometheus
      unarchive:
        src: "/tmp/prometheus-{{ prometheus_version }}.tar.gz"
        dest: /opt/prometheus
        remote_src: yes
        owner: prometheus
        group: prometheus
        extra_opts: [--strip-components=1]
      tags:
        - installation
        - prometheus
        - monitoring

    - name: Configure Prometheus
      template:
        src: prometheus.yml.j2
        dest: /etc/prometheus/prometheus.yml
        owner: prometheus
        group: prometheus
        mode: '0644'
      notify: restart prometheus
      tags:
        - configuration
        - prometheus
        - monitoring

    - name: Create Prometheus systemd service
      template:
        src: prometheus.service.j2
        dest: /etc/systemd/system/prometheus.service
        mode: '0644'
      notify:
        - reload systemd
        - restart prometheus
      tags:
        - services
        - prometheus
        - monitoring

    # Node Exporter Installation
    - name: Download Node Exporter
      get_url:
        url: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-amd64.tar.gz"
        dest: "/tmp/node_exporter-{{ node_exporter_version }}.tar.gz"
        mode: '0644'
      tags:
        - download
        - node_exporter
        - monitoring

    - name: Extract Node Exporter
      unarchive:
        src: "/tmp/node_exporter-{{ node_exporter_version }}.tar.gz"
        dest: /opt
        remote_src: yes
        owner: node_exporter
        group: node_exporter
        extra_opts: [--strip-components=1]
        creates: /opt/node_exporter
      tags:
        - installation
        - node_exporter
        - monitoring

    - name: Create Node Exporter systemd service
      template:
        src: node_exporter.service.j2
        dest: /etc/systemd/system/node_exporter.service
        mode: '0644'
      notify:
        - reload systemd
        - restart node_exporter
      tags:
        - services
        - node_exporter
        - monitoring

    # Grafana Installation
    - name: Add Grafana repository key
      apt_key:
        url: https://packages.grafana.com/gpg.key
        state: present
      when: ansible_facts['os_family'] == 'Debian'
      tags:
        - repositories
        - grafana
        - monitoring

    - name: Add Grafana repository
      apt_repository:
        repo: "deb https://packages.grafana.com/oss/deb stable main"
        state: present
      when: ansible_facts['os_family'] == 'Debian'
      tags:
        - repositories
        - grafana
        - monitoring

    - name: Install Grafana
      package:
        name: grafana
        state: present
      tags:
        - installation
        - grafana
        - monitoring

    - name: Configure Grafana
      template:
        src: grafana.ini.j2
        dest: /etc/grafana/grafana.ini
        backup: yes
      notify: restart grafana
      tags:
        - configuration
        - grafana
        - monitoring

    # Service Management
    - name: Start and enable monitoring services
      service:
        name: "{{ item }}"
        state: started
        enabled: yes
      loop:
        - prometheus
        - node_exporter
        - grafana-server
      tags:
        - services
        - monitoring

  handlers:
    - name: reload systemd
      systemd:
        daemon_reload: yes

    - name: restart prometheus
      service:
        name: prometheus
        state: restarted

    - name: restart node_exporter
      service:
        name: node_exporter
        state: restarted

    - name: restart grafana
      service:
        name: grafana-server
        state: restarted

Windows Automation Examples

Example 3: Windows IIS and Application Deployment

windows_iis_deployment.yml

---
- name: Windows IIS Web Application Deployment
  hosts: windows_web_servers
  vars:
    app_pool_name: "WebAppPool"
    website_name: "WebApplication"
    app_path: "C:\\inetpub\\webapp"
    db_connection_string: "Server=sqlserver;Database=WebAppDB;Integrated Security=true;"
  
  tasks:
    # IIS Feature Installation
    - name: Install IIS Web Server
      win_feature:
        name:
          - IIS-WebServerRole
          - IIS-WebServer
          - IIS-CommonHttpFeatures
          - IIS-HttpErrors
          - IIS-HttpLogging
          - IIS-RequestMonitor
          - IIS-StaticContent
          - IIS-DefaultDocument
          - IIS-DirectoryBrowsing
        state: present
        include_management_tools: yes
      tags:
        - iis
        - features
        - webserver

    - name: Install ASP.NET features
      win_feature:
        name:
          - IIS-NetFxExtensibility45
          - IIS-ISAPIExtensions
          - IIS-ISAPIFilter
          - IIS-ASPNET45
        state: present
      tags:
        - iis
        - aspnet
        - features
        - webserver

    - name: Install IIS Management features
      win_feature:
        name:
          - IIS-ManagementConsole
          - IIS-IIS6ManagementCompatibility
          - IIS-Metabase
        state: present
      tags:
        - iis
        - management
        - features

    # Application Pool Configuration
    - name: Create Application Pool
      win_iis_webapppool:
        name: "{{ app_pool_name }}"
        state: present
        attributes:
          processModel.identityType: ApplicationPoolIdentity
          processModel.idleTimeout: "00:00:00"
          recycleLimits.requests: 1000
          recycleLimits.schedule: "02:00:00"
      tags:
        - iis
        - apppool
        - configuration

    - name: Configure Application Pool advanced settings
      win_iis_webapppool:
        name: "{{ app_pool_name }}"
        attributes:
          processModel.loadUserProfile: true
          processModel.setProfileEnvironment: true
          processModel.maxProcesses: 1
          cpu.limit: 80000
          cpu.action: Throttle
      tags:
        - iis
        - apppool
        - configuration
        - performance

    # Website and Application Setup
    - name: Create application directory
      win_file:
        path: "{{ app_path }}"
        state: directory
      tags:
        - filesystem
        - application
        - setup

    - name: Create IIS Website
      win_iis_website:
        name: "{{ website_name }}"
        state: present
        port: 80
        ip: "*"
        hostname: "webapp.company.local"
        application_pool: "{{ app_pool_name }}"
        physical_path: "{{ app_path }}"
      tags:
        - iis
        - website
        - configuration

    - name: Set website bindings
      win_iis_webbinding:
        name: "{{ website_name }}"
        protocol: http
        port: 80
        ip: "*"
        hostname: "webapp.company.local"
        state: present
      tags:
        - iis
        - website
        - bindings
        - configuration

    # Application Files Deployment
    - name: Deploy web.config
      win_template:
        src: web.config.j2
        dest: "{{ app_path }}\\web.config"
      tags:
        - application
        - deployment
        - configuration

    - name: Deploy application files
      win_copy:
        src: "{{ item.src }}"
        dest: "{{ app_path }}\\{{ item.dest }}"
      loop:
        - { src: 'Default.aspx', dest: 'Default.aspx' }
        - { src: 'Default.aspx.cs', dest: 'Default.aspx.cs' }
        - { src: 'Global.asax', dest: 'Global.asax' }
      tags:
        - application
        - deployment
        - files

    - name: Set application directory permissions
      win_acl:
        path: "{{ app_path }}"
        user: "IIS_IUSRS"
        rights: FullControl
        type: allow
        state: present
        inherit: ContainerInherit,ObjectInherit
        propagation: None
      tags:
        - security
        - permissions
        - application

    # SSL Configuration
    - name: Import SSL certificate
      win_certificate_store:
        state: present
        cert_store: cert:\LocalMachine\My
        cert_data: "{{ ssl_certificate_data }}"
        cert_password: "{{ ssl_certificate_password }}"
        file_type: pkcs12
      when: ssl_certificate_data is defined
      tags:
        - ssl
        - certificates
        - security

    - name: Add HTTPS binding
      win_iis_webbinding:
        name: "{{ website_name }}"
        protocol: https
        port: 443
        ip: "*"
        hostname: "webapp.company.local"
        certificate_hash: "{{ ssl_certificate_thumbprint }}"
        certificate_store_name: "My"
        ssl_flags: 0
        state: present
      when: ssl_certificate_thumbprint is defined
      tags:
        - ssl
        - https
        - bindings
        - security

    # Performance and Monitoring
    - name: Install Performance Counters
      win_feature:
        name: IIS-HttpCompressionStatic
        state: present
      tags:
        - performance
        - compression
        - optimization

    - name: Configure IIS compression
      win_iis_webapppool:
        name: "{{ app_pool_name }}"
        attributes:
          processModel.pingInterval: "00:00:30"
          processModel.pingResponseTime: "00:01:30"
          processModel.shutdownTimeLimit: "00:01:30"
      tags:
        - performance
        - configuration
        - optimization

    # Logging Configuration
    - name: Configure IIS logging
      win_shell: |
        Import-Module WebAdministration
        Set-WebConfigurationProperty -Filter '/system.webServer/httpLogging' -Name 'directory' -Value 'C:\inetpub\logs\LogFiles' -PSPath 'IIS:\'
        Set-WebConfigurationProperty -Filter '/system.webServer/httpLogging' -Name 'logFormat' -Value 'W3C' -PSPath 'IIS:\'
      tags:
        - logging
        - configuration
        - monitoring

Example 4: Windows SQL Server Configuration

windows_sql_setup.yml

---
- name: Windows SQL Server Configuration
  hosts: windows_sql_servers
  vars:
    sql_sa_password: "SQLServerAdmin2023!"
    sql_service_account: "DOMAIN\\sql_service"
    sql_service_password: "ServiceAccount2023!"
  
  tasks:
    # SQL Server Installation Prerequisites
    - name: Install .NET Framework 4.8
      win_feature:
        name:
          - NET-Framework-45-Features
          - NET-Framework-45-Core
          - NET-Framework-45-ASPNET
        state: present
      tags:
        - prerequisites
        - dotnet
        - installation

    - name: Download SQL Server installation media
      win_get_url:
        url: "https://download.microsoft.com/download/sqlserver2019.iso"
        dest: "C:\\temp\\sqlserver2019.iso"
      tags:
        - download
        - installation
        - prerequisites

    # SQL Server Service Configuration
    - name: Configure SQL Server service account
      win_service:
        name: MSSQLSERVER
        username: "{{ sql_service_account }}"
        password: "{{ sql_service_password }}"
        start_mode: auto
        state: started
      tags:
        - services
        - security
        - configuration

    - name: Configure SQL Server Agent service
      win_service:
        name: SQLSERVERAGENT
        username: "{{ sql_service_account }}"
        password: "{{ sql_service_password }}"
        start_mode: auto
        state: started
      tags:
        - services
        - agent
        - configuration

    # Database Configuration
    - name: Create application databases
      win_shell: |
        sqlcmd -S localhost -E -Q "
        IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = '{{ item.name }}')
        BEGIN
          CREATE DATABASE [{{ item.name }}]
          ON (FILENAME = '{{ item.data_file }}')
          LOG ON (FILENAME = '{{ item.log_file }}')
        END"
      loop:
        - name: "WebAppDB"
          data_file: "C:\\SQLData\\WebAppDB.mdf"
          log_file: "C:\\SQLData\\WebAppDB.ldf"
        - name: "WebAppDB_Test"
          data_file: "C:\\SQLData\\WebAppDB_Test.mdf"
          log_file: "C:\\SQLData\\WebAppDB_Test.ldf"
      tags:
        - database
        - creation
        - application

    - name: Create database users
      win_shell: |
        sqlcmd -S localhost -E -Q "
        USE [{{ item.database }}]
        IF NOT EXISTS (SELECT name FROM sys.database_principals WHERE name = '{{ item.username }}')
        BEGIN
          CREATE USER [{{ item.username }}] FOR LOGIN [{{ item.login }}]
          ALTER ROLE {{ item.role }} ADD MEMBER [{{ item.username }}]
        END"
      loop:
        - database: "WebAppDB"
          username: "webapp_user"
          login: "DOMAIN\\webapp_service"
          role: "db_datareader"
        - database: "WebAppDB"
          username: "webapp_admin"
          login: "DOMAIN\\webapp_admin"
          role: "db_owner"
      tags:
        - database
        - users
        - security

    # Security Configuration
    - name: Configure SQL Server security
      win_shell: |
        sqlcmd -S localhost -E -Q "
        EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', 
        N'Software\Microsoft\MSSQLServer\MSSQLServer', 
        N'LoginMode', REG_DWORD, 2"
      tags:
        - security
        - authentication
        - configuration

    - name: Enable TCP/IP protocol
      win_shell: |
        Import-Module SQLPS -DisableNameChecking
        $MachineObject = New-Object ('Microsoft.SqlServer.Management.Smo.WMI.ManagedComputer') localhost
        $ProtocolUri = "ManagedComputer[@Name='$env:COMPUTERNAME']/ServerInstance[@Name='MSSQLSERVER']/ServerProtocol[@Name='Tcp']"
        $Tcp = $MachineObject.GetSmoObject($ProtocolUri)
        $Tcp.IsEnabled = $true
        $Tcp.Alter()
      tags:
        - network
        - protocols
        - configuration

    # Backup Configuration
    - name: Create backup directories
      win_file:
        path: "{{ item }}"
        state: directory
      loop:
        - "C:\\SQLBackups\\Full"
        - "C:\\SQLBackups\\Differential"
        - "C:\\SQLBackups\\Transaction"
      tags:
        - backup
        - directories
        - setup

    - name: Configure database backup jobs
      win_shell: |
        sqlcmd -S localhost -E -Q "
        USE msdb
        EXEC dbo.sp_add_job 
          @job_name = N'{{ item.job_name }}',
          @enabled = 1,
          @description = N'{{ item.description }}'
        
        EXEC dbo.sp_add_jobstep
          @job_name = N'{{ item.job_name }}',
          @step_name = N'Backup Step',
          @command = N'{{ item.command }}'
        
        EXEC dbo.sp_add_schedule
          @schedule_name = N'{{ item.schedule_name }}',
          @freq_type = {{ item.freq_type }},
          @freq_interval = {{ item.freq_interval }},
          @active_start_time = {{ item.start_time }}
        
        EXEC dbo.sp_attach_schedule
          @job_name = N'{{ item.job_name }}',
          @schedule_name = N'{{ item.schedule_name }}'
        
        EXEC dbo.sp_add_jobserver
          @job_name = N'{{ item.job_name }}'"
      loop:
        - job_name: "Full Backup - WebAppDB"
          description: "Full backup of WebAppDB"
          command: "BACKUP DATABASE [WebAppDB] TO DISK = N'C:\\SQLBackups\\Full\\WebAppDB_Full.bak'"
          schedule_name: "Daily Full Backup"
          freq_type: 4
          freq_interval: 1
          start_time: 20000
      tags:
        - backup
        - jobs
        - automation

    # Performance Monitoring
    - name: Configure SQL Server performance counters
      win_shell: |
        sqlcmd -S localhost -E -Q "
        EXEC sp_configure 'show advanced options', 1
        RECONFIGURE
        EXEC sp_configure 'max server memory', {{ ansible_memtotal_mb * 0.8 | int }}
        RECONFIGURE"
      tags:
        - performance
        - memory
        - configuration

    - name: Install SQL Server management tools
      win_package:
        path: "https://download.microsoft.com/download/SSMS-Setup-ENU.exe"
        arguments: "/quiet /install"
        state: present
      tags:
        - tools
        - management
        - installation

Sequence Diagram: Ansible Tags Workflow

Here's a sequence diagram illustrating how Ansible processes tags during playbook execution:

This diagram illustrates the key aspects of tag processing:

  1. Tag parsing happens before execution

  2. Filtering occurs based on command-line options

  3. Special tags (always/never) have precedence rules

  4. Only filtered tasks are executed on target hosts

Special Tags

Always and Never Tags

Ansible provides two special tags with unique behaviors:

Always Tag: Tasks tagged with always run by default, unless explicitly skipped

- name: Security check that always runs
  command: /usr/bin/security-scan
  tags:
    - always
    - security

# This task runs in ALL of these scenarios:
# ansible-playbook site.yml
# ansible-playbook site.yml --tags "webserver"
# ansible-playbook site.yml --tags "database"

# This task is skipped ONLY when:
# ansible-playbook site.yml --skip-tags "always"
# ansible-playbook site.yml --skip-tags "security"

Never Tag: Tasks tagged with never are skipped by default, unless explicitly requested

- name: Dangerous operation that rarely runs
  command: /usr/bin/dangerous-cleanup
  tags:
    - never
    - cleanup

# This task is skipped in ALL of these scenarios:
# ansible-playbook site.yml
# ansible-playbook site.yml --tags "webserver"
# ansible-playbook site.yml --tags "database"

# This task runs ONLY when:
# ansible-playbook site.yml --tags "never"
# ansible-playbook site.yml --tags "cleanup"
# ansible-playbook site.yml --tags "never,cleanup"

Practical Example with Special Tags

maintenance_playbook.yml

---
- name: System maintenance with special tags
  hosts: all
  become: yes
  tasks:
    - name: Check system health (always runs)
      command: systemctl status
      register: system_status
      tags:
        - always
        - health_check

    - name: Regular application update
      package:
        name: myapp
        state: latest
      tags:
        - update
        - application

    - name: Emergency database rebuild (never runs unless requested)
      shell: |
        systemctl stop myapp
        mysql -u root -p{{ mysql_root_password }} -e "DROP DATABASE myapp_db; CREATE DATABASE myapp_db;"
        systemctl start myapp
      tags:
        - never
        - emergency
        - database_rebuild

    - name: Send maintenance notification
      mail:
        to: [email protected]
        subject: "Maintenance completed on {{ inventory_hostname }}"
        body: "System maintenance completed successfully"
      tags:
        - always
        - notification

Usage examples:

# Regular maintenance (runs health check, update, and notification)
ansible-playbook maintenance_playbook.yml --tags "update"

# Emergency scenario (runs health check, database rebuild, and notification)
ansible-playbook maintenance_playbook.yml --tags "emergency"

# Health check only (runs only the health check task)
ansible-playbook maintenance_playbook.yml --tags "health_check" --skip-tags "always"

Advanced Tag Techniques

Tag Inheritance

Tags applied at higher levels (plays, blocks, roles) are inherited by all contained tasks:

---
- name: Web server setup
  hosts: web_servers
  tags: webserver  # This tag applies to ALL tasks in this play
  tasks:
    - name: Install Apache
      package:
        name: apache2
        state: present
      # Inherits 'webserver' tag automatically

    - name: Configure virtual hosts
      template:
        src: vhost.conf.j2
        dest: /etc/apache2/sites-available/mysite.conf
      # Also inherits 'webserver' tag

- name: Database setup
  hosts: db_servers
  tags: database  # This tag applies to ALL tasks in this play
  tasks:
    - name: Install MySQL
      package:
        name: mysql-server
        state: present
      # Inherits 'database' tag

Block-Level Tagging

Group related tasks with block-level tags:

- name: SSL Certificate Management
  block:
    - name: Generate private key
      openssl_privatekey:
        path: /etc/ssl/private/mysite.key
        size: 2048

    - name: Generate certificate signing request
      openssl_csr:
        path: /etc/ssl/certs/mysite.csr
        privatekey_path: /etc/ssl/private/mysite.key
        common_name: mysite.company.com

    - name: Generate self-signed certificate
      openssl_certificate:
        path: /etc/ssl/certs/mysite.crt
        privatekey_path: /etc/ssl/private/mysite.key
        csr_path: /etc/ssl/certs/mysite.csr
        provider: selfsigned
  tags:
    - ssl
    - certificates
    - security

Role-Level Tagging

Apply tags to entire roles:

---
- name: Multi-service deployment
  hosts: app_servers
  roles:
    - role: webserver
      tags: 
        - web
        - frontend
    
    - role: database
      tags:
        - db
        - backend
    
    - role: monitoring
      tags:
        - monitoring
        - observability

Tag Management Best Practices

Consistent Naming Convention

Establish and follow a consistent tag naming convention:

# Good: Consistent, descriptive naming
tags:
  - packages          # For package installation tasks
  - configuration     # For configuration file management
  - services         # For service management
  - security         # For security-related tasks
  - application      # For application deployment
  - monitoring       # For monitoring setup

# Avoid: Inconsistent naming
tags:
  - pkg              # Too abbreviated
  - config-files     # Mixed conventions
  - svc             # Unclear abbreviation

Hierarchical Tag Organization

Use hierarchical tags for complex deployments:

# Hierarchical tagging example
- name: Install web server
  package:
    name: nginx
    state: present
  tags:
    - install
    - install.packages
    - install.packages.webserver
    - webserver

- name: Configure web server
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  tags:
    - configure
    - configure.webserver
    - configure.webserver.main
    - webserver

Environment-Specific Tags

Use tags to manage different environments:

---
- name: Deploy application
  hosts: all
  tasks:
    - name: Deploy to development
      copy:
        src: app-dev.jar
        dest: /opt/app/app.jar
      when: environment == "development"
      tags:
        - deploy
        - development

    - name: Deploy to production
      copy:
        src: app-prod.jar
        dest: /opt/app/app.jar
      when: environment == "production"
      tags:
        - deploy
        - production
        - critical

Tag Documentation

Document your tagging strategy:

# Project: Web Application Deployment
# Tag Strategy:
#   - packages: All package installation tasks
#   - webserver: Web server related tasks (nginx, apache)
#   - database: Database related tasks (mysql, postgresql)
#   - application: Application deployment and configuration
#   - security: Security hardening and configuration
#   - monitoring: Monitoring and logging setup
#   - development: Development environment specific tasks
#   - production: Production environment specific tasks
#   - always: Tasks that should always run
#   - never: Tasks that should only run when explicitly requested

---
- name: Web application deployment
  hosts: web_servers
  tasks:
    # ... tasks with documented tags

Real-World Multi-Tier Deployment Example

Here's a comprehensive example showing how to use tags for a complex multi-tier application deployment:

multi_tier_deployment.yml

---
- name: Multi-tier application deployment
  hosts: all
  become: yes
  vars:
    app_version: "{{ deploy_version | default('latest') }}"
    environment: "{{ target_environment | default('development') }}"
  
  pre_tasks:
    - name: Validate deployment parameters
      assert:
        that:
          - app_version is defined
          - environment in ['development', 'staging', 'production']
        fail_msg: "Invalid deployment parameters"
      tags:
        - always
        - validation

  tasks:
    # Load Balancer Preparation
    - name: Remove servers from load balancer
      uri:
        url: "http://{{ item }}:8080/remove/{{ inventory_hostname }}"
        method: POST
      loop: "{{ groups['load_balancers'] | default([]) }}"
      delegate_to: localhost
      when: "'web_servers' in group_names"
      tags:
        - loadbalancer
        - preparation
        - rolling_update

    # Database Tier
    - name: Update database schema
      mysql_db:
        name: "{{ app_database }}"
        state: import
        target: "/tmp/schema-{{ app_version }}.sql"
      when: "'database_servers' in group_names"
      tags:
        - database
        - schema
        - migration

    - name: Run database migrations
      command: >
        /opt/app/bin/migrate
        --version {{ app_version }}
        --environment {{ environment }}
      when: "'database_servers' in group_names"
      tags:
        - database
        - migration
        - data

    # Application Tier
    - name: Stop application services
      service:
        name: "{{ app_service_name }}"
        state: stopped
      when: "'app_servers' in group_names"
      tags:
        - application
        - services
        - deployment

    - name: Backup current application
      archive:
        path: /opt/app
        dest: "/tmp/app-backup-{{ ansible_date_time.epoch }}.tar.gz"
      when: "'app_servers' in group_names and environment == 'production'"
      tags:
        - application
        - backup
        - production

    - name: Deploy new application version
      unarchive:
        src: "artifacts/app-{{ app_version }}.tar.gz"
        dest: /opt/app
        owner: app
        group: app
        mode: '0755'
      when: "'app_servers' in group_names"
      tags:
        - application
        - deployment
        - files

    - name: Update application configuration
      template:
        src: "app.conf.j2"
        dest: "/opt/app/config/app.conf"
        owner: app
        group: app
        mode: '0640'
      when: "'app_servers' in group_names"
      notify: restart application
      tags:
        - application
        - configuration
        - deployment

    # Web Tier
    - name: Update web server configuration
      template:
        src: "nginx-{{ environment }}.conf.j2"
        dest: "/etc/nginx/sites-available/{{ app_name }}"
      when: "'web_servers' in group_names"
      notify: reload nginx
      tags:
        - webserver
        - configuration
        - nginx

    - name: Deploy static assets
      synchronize:
        src: "static/{{ app_version }}/"
        dest: "/var/www/{{ app_name }}/static/"
        delete: yes
      when: "'web_servers' in group_names"
      tags:
        - webserver
        - static_assets
        - deployment

    # Health Checks
    - name: Wait for application to start
      wait_for:
        port: "{{ app_port }}"
        host: "{{ inventory_hostname }}"
        delay: 10
        timeout: 300
      when: "'app_servers' in group_names"
      tags:
        - health_check
        - verification
        - always

    - name: Verify application health
      uri:
        url: "http://{{ inventory_hostname }}:{{ app_port }}/health"
        method: GET
        status_code: 200
      when: "'app_servers' in group_names"
      retries: 5
      delay: 10
      tags:
        - health_check
        - verification
        - always

    # Load Balancer Restoration
    - name: Add servers back to load balancer
      uri:
        url: "http://{{ item }}:8080/add/{{ inventory_hostname }}"
        method: POST
      loop: "{{ groups['load_balancers'] | default([]) }}"
      delegate_to: localhost
      when: "'web_servers' in group_names"
      tags:
        - loadbalancer
        - restoration
        - rolling_update

  post_tasks:
    - name: Send deployment notification
      mail:
        to: "{{ notification_email }}"
        subject: "Deployment completed: {{ app_name }} v{{ app_version }}"
        body: |
          Deployment completed successfully:
          - Application: {{ app_name }}
          - Version: {{ app_version }}
          - Environment: {{ environment }}
          - Host: {{ inventory_hostname }}
          - Timestamp: {{ ansible_date_time.iso8601 }}
      tags:
        - notification
        - always

  handlers:
    - name: restart application
      service:
        name: "{{ app_service_name }}"
        state: restarted

    - name: reload nginx
      service:
        name: nginx
        state: reloaded

Usage Examples for Multi-Tier Deployment:

# Full deployment
ansible-playbook multi_tier_deployment.yml

# Database-only deployment
ansible-playbook multi_tier_deployment.yml --tags "database"

# Application-only deployment (skip database)
ansible-playbook multi_tier_deployment.yml --skip-tags "database"

# Rolling update (web servers only)
ansible-playbook multi_tier_deployment.yml --tags "webserver,rolling_update" --limit "web_servers"

# Production deployment with extra safety
ansible-playbook multi_tier_deployment.yml --tags "production,backup" -e "environment=production"

# Emergency rollback (never runs unless explicitly requested)
ansible-playbook multi_tier_deployment.yml --tags "never,rollback"

# Health check only
ansible-playbook multi_tier_deployment.yml --tags "health_check,verification"

# Preview what would run for application deployment
ansible-playbook multi_tier_deployment.yml --tags "application" --list-tasks

Conclusion

Ansible Tags have fundamentally changed how I approach automation development and deployment. What started as a frustration with slow, monolithic playbook executions has evolved into a sophisticated workflow that enables rapid development, precise deployments, and efficient troubleshooting.

The power of tags lies not just in their ability to selectively execute tasks, but in how they encourage better playbook organization and design. When you start thinking about how to tag your tasks, you naturally begin to group related functionality and create more modular, maintainable automation code.

Remember that effective tagging is both an art and a science. Start with simple, descriptive tags and gradually develop more sophisticated strategies as your automation needs grow. Document your tagging conventions, use consistent naming patterns, and always consider how tags will be used by other team members.

As you implement tags in your own projects, you'll find that they become indispensable for:

  • Development workflows - Testing specific components without running everything

  • Production deployments - Rolling out changes incrementally and safely

  • Troubleshooting - Isolating and re-running problematic tasks

  • Maintenance operations - Performing specific maintenance tasks across your infrastructure

The investment in learning and implementing a good tagging strategy will pay dividends in improved automation efficiency, reduced deployment times, and more reliable operations.

Last updated