- Ansible is agentless config management — SSH into machines and apply YAML playbooks
- Inventory defines hosts; Playbooks define what to do; Modules do the work
- Idempotent by default — running the same playbook twice is safe
- ansible-playbook runs playbooks; ansible runs single ad-hoc commands
- Roles organize playbooks into reusable components
- Vault encrypts sensitive data (passwords, API keys) in playbooks
- No agent needed on remote hosts — only Python and SSH
Quick reference tables
Installation
| Platform | Command |
|---|---|
| macOS | brew install ansible |
| Ubuntu / Debian | sudo apt install ansible |
| pip | pip install ansible |
| pipx | pipx install ansible-core |
| Verify | ansible --version |
Core CLI commands
| Command | What it does |
|---|---|
ansible all -m ping | Ping all hosts in inventory |
ansible all -m shell -a "uptime" | Run shell command on all hosts |
ansible all -m copy -a "src=x dest=y" | Copy file to all hosts |
ansible-playbook site.yml | Run a playbook |
ansible-playbook site.yml --check | Dry-run (diff without applying) |
ansible-playbook site.yml --tags web | Run only tasks tagged “web” |
ansible-playbook site.yml --start-at-task="Deploy" | Start from a specific task |
ansible-vault encrypt secrets.yml | Encrypt a file with vault |
ansible-galaxy init myrole | Scaffold a new role |
ansible-inventory -i inv.ini --list | List all inventory hosts |
Essential modules
| Module | Use for |
|---|---|
shell | Run arbitrary shell commands |
command | Run commands (no shell special chars) |
copy | Copy files to remote hosts |
template | Copy template files (Jinja2) |
file | Create/delete files and directories |
lineinfile | Insert or replace a line in a file |
apt / yum / dnf | Package management |
service / systemd | Start/stop services |
git | Clone and manage git repos |
fetch | Pull files from remote to local |
user | Create and manage users |
firewalld / ufw | Firewall management |
docker_container | Manage Docker containers |
set_fact | Set variables for a host |
Inventory
INI format (inventory.ini)
[webservers]
web1.example.com ansible_user=ubuntu
web2.example.com ansible_user=ubuntu
[dbservers]
db1.example.com ansible_user=postgres
db2.example.com ansible_user=postgres
[production:children]
webservers
dbservers
[production:vars]
ansible_python_interpreter=/usr/bin/python3
environment=production YAML format (inventory.yml)
all:
children:
webservers:
hosts:
web1.example.com:
ansible_user: ubuntu
web2.example.com:
ansible_user: ubuntu
dbservers:
hosts:
db1.example.com:
ansible_user: postgres
vars:
ansible_python_interpreter: /usr/bin/python3
environment: production Dynamic inventory
# Use AWS EC2 dynamic inventory
ansible-playbook site.yml -i ec2.py
# Use a custom script
ansible-playbook site.yml -i inventory script.py --list-hosts Playbooks
Minimal playbook
---
- name: Basic setup
hosts: webservers
become: yes # Run as root
vars:
app_dir: /opt/myapp
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Copy config file
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: Restart nginx
- name: Start nginx
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted Roles
Role structure
roles/
└── webserver/
├── defaults/
│ └── main.yml # Default variables (lowest priority)
├── files/
│ └── nginx.conf # Files to copy
├── handlers/
│ └── main.yml # Handlers
├── meta/
│ └── main.yml # Role dependencies
├── tasks/
│ └── main.yml # Main tasks
├── templates/
│ └── nginx.conf.j2 # Jinja2 templates
└── vars/
└── main.yml # Higher-priority variables tasks/main.yml
---
- name: Deploy web application
block:
- name: Create application directory
file:
path: "{{ app_dir }}"
state: directory
owner: "{{ app_user }}"
mode: '0755'
- name: Clone repository
git:
repo: "{{ app_repo }}"
dest: "{{ app_dir }}"
version: "{{ app_branch }}"
force: yes
- name: Install dependencies
pip:
requirements: "{{ app_dir }}/requirements.txt"
virtualenv: "{{ app_dir }}/venv"
- name: Start application
systemd:
name: "{{ app_service }}"
state: started
enabled: yes
daemon_reload: yes
register: app_started
rescue:
- name: Rollback notification
debug:
msg: "Application deployment failed. Rolling back." Playbook using roles
---
- name: Production deployment
hosts: production
become: yes
roles:
- role: common # Standard roles
- role: webserver
vars:
app_dir: /opt/myapp
app_service: myapp
- role: database
when: "'dbservers' in group_names" Conditionals and loops
Conditionals
- name: Start service based on OS
service:
name: "{{ service_name }}"
state: started
when: ansible_os_family == "Debian"
- name: Create user if not exists
user:
name: deploy
state: present
shell: /bin/bash
when: "'dbservers' in group_names"
- name: Check if file exists
stat:
path: /etc/app.conf
register: config_file
- name: Copy config only if missing
copy:
src: app.conf
dest: /etc/app.conf
when: not config_file.stat.exists Loops
# Simple loop
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- curl
# Loop with index
- name: Create users
user:
name: "{{ item.user }}"
uid: "{{ item.uid }}"
shell: /bin/bash
loop:
- { user: alice, uid: 1001 }
- { user: bob, uid: 1002 }
- { user: carol, uid: 1003 }
# Loop over dict
- name: Set facts
set_fact:
"{{ item.key }}": "{{ item.value }}"
loop:
- { key: env, value: production }
- { key: tier, value: backend } Error handling (blocks)
- name: Deploy application
block:
- name: Pull latest code
git:
repo: "{{ app_repo }}"
dest: "{{ app_dir }}"
version: "{{ app_version }}"
- name: Run migrations
command: "{{ app_dir }}/migrate.sh"
changed_when: true
rescue:
- name: Notify on failure
debug:
msg: "Deployment failed — check logs"
- name: Run rollback script
command: "{{ app_dir }}/rollback.sh" Vault (secrets)
# Create an encrypted file
ansible-vault encrypt secrets.yml
# Edit an encrypted file
ansible-vault edit secrets.yml
# Create a new encrypted file
ansible-vault create secrets.yml
# Decrypt to view
ansible-vault decrypt secrets.yml
# Change password
ansible-vault rekey secrets.yml
# Encrypt a string (for CLI use)
ansible-vault encrypt_string "mypassword" --name "db_password" Using vault in playbooks
# Run playbook with vault password
ansible-playbook site.yml --ask-vault-pass
# Use a password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass # secrets.yml (encrypted)
---
db_host: prod-db.example.com
db_user: appuser
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
616263646566... # encrypted value # playbook.yml
- name: Deploy application
hosts: dbservers
vars_files:
- secrets.yml # Decrypted automatically
tasks:
- name: Configure database connection
template:
src: db.conf.j2
dest: /etc/app/db.conf Common patterns
Check mode (dry run)
ansible-playbook site.yml --check # Show what would change
ansible-playbook site.yml --check --diff # Show diffs Tags
tasks:
- name: Install dependencies
apt:
name: "{{ item }}"
loop:
- nginx
- git
tags:
- setup
- web
- name: Deploy app
git:
repo: "{{ app_repo }}"
dest: "{{ app_dir }}"
tags:
- deploy ansible-playbook site.yml --tags setup # Run only setup tasks
ansible-playbook site.yml --skip-tags deploy # Skip deploy tasks Delegation and local actions
- name: Deploy to web servers one by one
command: /opt/deploy.sh
hosts: webservers
serial: 1 # Run on one host at a time
- name: Notify localhost
debug:
msg: "Deployment complete on {{ inventory_hostname }}"
delegate_to: localhost
- name: Wait for web server to be ready
wait_for:
host: "{{ item }}"
port: 80
delay: 5
timeout: 30
loop: "{{ groups['webservers'] }}" Jinja2 filters
- name: Set fact with filters
set_fact:
app_version_clean: "{{ app_version | trim | upper }}"
config_dict: "{{ config_json | from_json }}"
filtered_list: "{{ items_list | selectattr('active') | list }}"
joined: "{{ domains | join(', ') }}" Summary
- Inventory defines hosts; Playbooks define tasks; Modules do the work
- Roles organize tasks, handlers, templates, and files into reusable units
become: yesruns tasks as root;when:applies conditionals- Vault encrypts secrets;
--ask-vault-passor--vault-password-fileunlocks them block/rescuehandles errors gracefully with rollback logic- Check mode (
--check) and--tagsare your best debugging tools
FAQ
What is the difference between shell and command modules?
command runs the command directly (no shell processing). shell runs through /bin/sh, so it supports pipes, redirects, and environment variables. Use command when possible for security and predictability.
How do I handle host key verification errors with SSH?
Add to ansible.cfg: host_key_checking = False. For production, pre-authorize host keys or use sshpass with a known-hosts file.
Can Ansible manage Windows hosts?
Yes. WinRM (over PowerShell) is the primary method. Install pywinrm on the control node and configure WinRM on Windows hosts. Supported since Ansible 2.4.
How do I run a task on localhost without SSH?
Use hosts: localhost and set connection: local in the play, or delegate_to: localhost for specific tasks.
Is Ansible idempotent?
Yes, by design. Running a playbook twice should produce the same result as running it once. Use state: present/absent instead of raw apt install / apt remove to get idempotency.
What to read next
- Docker Cheat Sheet — containerize the apps Ansible configures
- GitHub Actions Cheat Sheet — run Ansible in CI/CD pipelines
- Linux Bash Cheat Sheet — shell scripting for Ansible task chains
Related Articles
Deepen your understanding with these curated continuations.
jq Cheat Sheet: Filter, Transform, and Query JSON from the Terminal
Complete jq reference — basic filters, pipes, array/object operations, conditionals, string interpolation, select, map, reduce, and real DevOps workflows.
ClickHouse Cheat Sheet: Database Commands, Schema Design & Performance (2026)
Complete ClickHouse reference — SELECT patterns, aggregations, array functions, window functions, schema design, materialized views, row-level security, and performance tuning in 2026.
Cloudflare Workers Cheat Sheet: Edge Functions, Durable Objects & Storage
Complete Cloudflare Workers reference — Hono/fastify on the edge, Workers AI inference, KV, R2, D1, Durable Objects, queues, and deployment with Wrangler in 2026.