Ad-hoc Commands and Playbooks
Last updated: June 29, 2025
From Quick Tasks to Orchestrated Automation
When I first started with Ansible, I found myself in a situation where I needed to check disk space on 50+ servers immediately. I didn't have time to write a script, and logging into each server individually would have taken hours. That's when I discovered the power of Ansible ad-hoc commands. With a single line, I was able to check disk usage across all my servers in seconds. Later, as my automation needs grew more complex, I graduated to playbooks for repeatable, documented automation. In this post, I'll share my journey with both these powerful Ansible features.
Ad-hoc Commands: The Command-Line Magic
Ad-hoc commands are Ansible's way of providing command-line magic for one-off tasks. They're perfect for when you need to do something quick without the overhead of creating a playbook. Think of them as the Swiss Army knife in your Ansible toolkit.
The Basic Syntax
The basic syntax of an ad-hoc command is:
ansible [pattern] -m [module] -a "[module options]"
Where:
[pattern]
is the host or group you're targeting-m [module]
specifies which module to use (defaults tocommand
if omitted)-a "[module options]"
provides arguments to the module
When to Use Ad-hoc Commands
I've found ad-hoc commands invaluable for:
Quick checks: Verifying system status across multiple servers
Simple changes: Making the same change on multiple systems
Emergency fixes: Addressing urgent issues quickly
Fact gathering: Collecting information about your infrastructure
Testing: Confirming connectivity or module behavior before adding to playbooks
Real-world Examples for Linux
Checking System Status
One of the first things I do when troubleshooting is check system load across all affected servers:
ansible webservers -m ansible.builtin.shell -a "uptime"
Or check disk space usage:
ansible all -m ansible.builtin.shell -a "df -h"
Running Commands with Privilege Escalation
For tasks that require root access:
ansible webservers -m ansible.builtin.shell -a "tail /var/log/secure" --become
The --become
flag (or -b
for short) makes Ansible use privilege escalation, running the command as root.
Managing Packages Quickly
Need to install a package on all database servers? No problem:
ansible dbservers -m ansible.builtin.yum -a "name=postgresql state=latest" --become
Or remove a vulnerable package from everywhere:
ansible all -m ansible.builtin.apt -a "name=vulnerable-package state=absent" --become
Real-world Examples for Windows
Ad-hoc commands work just as well for Windows systems when you have the right setup:
Checking Windows Services
ansible windows_servers -m ansible.windows.win_service -a "name=Spooler"
Installing Windows Updates
ansible windows_servers -m ansible.windows.win_updates -a "category_names=['SecurityUpdates', 'CriticalUpdates']"
Checking Disk Space on Windows
ansible windows_servers -m ansible.windows.win_shell -a "Get-PSDrive C | Select-Object Used,Free"
Playbooks: Automation as Code
While ad-hoc commands are great for one-off tasks, I quickly found myself repeating certain sequences of operations. This is where Ansible playbooks shine. Playbooks are like automation scripts on steroids – they define a set of tasks to be executed in sequence, with powerful features like variables, conditionals, and loops.
Anatomy of a Playbook
Playbooks are written in YAML format. Here's the basic structure:
---
- name: Play name
hosts: target_hosts
become: yes # If you need privilege escalation
tasks:
- name: First task description
module_name:
option1: value1
option2: value2
- name: Second task description
another_module:
option: value
The Power of Idempotence
One thing I love about Ansible is its idempotent nature. This means you can run a playbook multiple times, and after the first run, no changes will be made unless they're needed. This gives me confidence to run playbooks regularly to ensure systems stay in the desired state.
My First Multi-OS Playbook Journey
Early in my Ansible journey, I needed to create a playbook that would work across both Linux and Windows servers. Here's a simplified version of what I created:
---
- name: Configure common settings across all servers
hosts: all
become: yes # Will be ignored on Windows if using SSH
tasks:
# Task for both Linux and Windows
- name: Ensure NTP is running
block:
- name: Configure NTP on Linux
ansible.builtin.service:
name: "{{ 'chronyd' if ansible_os_family == 'RedHat' else 'ntp' }}"
state: started
enabled: yes
when: ansible_os_family != "Windows"
- name: Configure NTP on Windows
ansible.windows.win_service:
name: W32Time
state: started
start_mode: auto
when: ansible_os_family == "Windows"
# Security updates
- name: Apply security updates
block:
- name: Apply Linux security updates
ansible.builtin.package:
name: "*"
state: latest
security: yes
when: ansible_os_family != "Windows"
- name: Apply Windows security updates
ansible.windows.win_updates:
category_names:
- SecurityUpdates
reboot: no
when: ansible_os_family == "Windows"
This playbook taught me the value of using conditionals based on the operating system (ansible_os_family
) to apply the right tasks to the right systems.
Sequence Diagram: Ad-hoc Command vs. Playbook Execution
To better understand how ad-hoc commands and playbooks work under the hood, let me share a sequence diagram that compares their execution flows:
The key differences illustrated here are:
Ad-hoc commands execute a single module once
Playbooks organize multiple tasks with logic and structure
Playbook execution includes variable processing and handler management
Best Practices I've Learned
After years of using Ansible, I've developed some best practices that have saved me countless hours:
For Ad-hoc Commands
Use
--limit
for testing: When trying a new command, limit it to one server first.ansible all -m ping --limit test-server-01
Leverage
--check
mode: See what would change without making actual changes.ansible webservers -m yum -a "name=httpd state=latest" --check
Increase parallelism for large environments: Use
-f
to set the number of parallel processes.ansible all -m ping -f 50
Save complex ad-hoc commands: If you find yourself using the same ad-hoc command regularly, it's time for a playbook.
For Playbooks
Start simple, grow incrementally: Begin with a basic playbook and add complexity as needed.
Use roles for organization: As your playbooks grow, refactor common tasks into roles for reusability.
Always test playbooks in dev: Never run a new playbook against production servers first.
Use variables for environment-specific values: This keeps your playbooks flexible.
# group_vars/production.yml ntp_server: prod-ntp.example.com # group_vars/development.yml ntp_server: dev-ntp.example.com # In your playbook - name: Set NTP server template: src: ntp.conf.j2 dest: /etc/ntp.conf
Include helpful comments: Your future self will thank you.
- name: Configure database settings # This must run after the schema initialization # See PROJ-1234 for details on the required settings template: src: db_config.j2 dest: /etc/app/database.conf
Real-world Playbook Examples
Linux Server Hardening
Here's a simplified version of a security hardening playbook I've used:
---
- name: Basic Linux hardening
hosts: linux_servers
become: yes
tasks:
- name: Ensure key security packages are installed
package:
name:
- fail2ban
- auditd
- rkhunter
state: present
- name: Configure SSH to disable root login
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
notify: Restart SSH
- name: Set strong password policies
blockinfile:
path: /etc/security/pwquality.conf
block: |
minlen = 12
dcredit = -1
ucredit = -1
ocredit = -1
lcredit = -1
handlers:
- name: Restart SSH
service:
name: sshd
state: restarted
Windows Server Setup
And here's a playbook for setting up IIS on Windows servers:
---
- name: Configure IIS Web Servers
hosts: windows_web_servers
tasks:
- name: Install IIS
win_feature:
name: Web-Server
state: present
include_management_tools: yes
register: iis_install
- name: Reboot if required
win_reboot:
when: iis_install.reboot_required
- name: Create website directory
win_file:
path: C:\websites\{{ website_name }}
state: directory
- name: Deploy website content
win_copy:
src: files/website/
dest: C:\websites\{{ website_name }}
- name: Configure new website
win_iis_website:
name: "{{ website_name }}"
state: started
port: 80
physical_path: C:\websites\{{ website_name }}
application_pool: "{{ website_name }}_pool"
Migrating from Ad-hoc to Playbooks
As your automation needs grow, you'll likely start with ad-hoc commands and gradually move to playbooks. Here's the process I follow:
Identify repetitive ad-hoc commands: If you're running the same ad-hoc command regularly, it's a candidate for a playbook.
Document the steps: Write down what you're trying to accomplish.
Create a simple playbook: Start with a basic version that does what your ad-hoc command did.
Test and expand: Test your playbook, then gradually add error handling, variables, and conditionals.
Refactor into roles: As your playbook grows, consider splitting it into reusable roles.
For example, I started with this ad-hoc command to check disk space:
ansible webservers -m ansible.builtin.shell -a "df -h | grep -v tmpfs"
Which evolved into this playbook for monitoring and alerting:
---
- name: Check disk space and alert if needed
hosts: all
gather_facts: yes
tasks:
- name: Get disk usage
ansible.builtin.shell: df -h / | tail -n 1 | awk '{print $5}' | sed 's/%//'
register: disk_usage
changed_when: false
- name: Alert if disk usage is high
debug:
msg: "WARNING: Disk usage on {{ inventory_hostname }} is {{ disk_usage.stdout }}%"
when: disk_usage.stdout|int > 90
- name: Send alert email
mail:
to: "{{ alert_email }}"
subject: "High disk usage alert for {{ inventory_hostname }}"
body: "Disk usage is at {{ disk_usage.stdout }}% on {{ inventory_hostname }}"
when: disk_usage.stdout|int > 90
Conclusion: The Right Tool for the Job
After years of working with Ansible, I've come to appreciate having both ad-hoc commands and playbooks in my toolbox. They each have their place:
Ad-hoc commands are perfect for quick, one-time tasks, troubleshooting, and information gathering.
Playbooks shine for repeatable processes, complex orchestration, and documented automation.
The best Ansible users know when to use each tool. Start with ad-hoc commands for simplicity, then graduate to playbooks as your automation needs mature. Together, they form a powerful combination that can tackle virtually any automation challenge across both Linux and Windows environments.
The journey from running a simple command on multiple servers to orchestrating complex, multi-tier application deployments becomes much more manageable when you master these two fundamental aspects of Ansible.
What's your favorite ad-hoc command or playbook trick? I'd love to hear your experiences in the comments below!
Last updated