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:
Selective execution - Run only the tasks you need
Skip unnecessary steps - Avoid time-consuming operations when not needed
Environment-specific tasks - Target different environments with precision
Debugging and testing - Isolate specific functionality for troubleshooting
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
always
TagTasks 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
never
TagTasks 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:
Start simple - Begin with basic component tags (webserver, database, monitoring)
Be consistent - Establish naming conventions and stick to them
Think in layers - Use tags for infrastructure, application, and operational concerns
Plan for scale - Design your tagging strategy to grow with your infrastructure
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:
Tag parsing happens before execution
Filtering occurs based on command-line options
Special tags (always/never) have precedence rules
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