What Is Ansible — Agentless Automation
Ansible is an automation tool that declares server configuration, deployment, and operations as code (YAML) and applies it consistently across many servers. Its defining trait is that it is agentless — it does not install a separate daemon on target servers; SSH and Python are all it needs.
- Idempotency: Running the same playbook any number of times produces the same result. "If the desired state is already in place, leave it alone."
- Declarative: You describe what should be in which state, and Ansible compares that with the current state and applies only the necessary changes.
- Push model: The control node pushes commands to target nodes over SSH.
A shell script is "a list of commands to run," so running it twice easily causes side effects. Most Ansible modules are idempotent, so repeated runs are safe — that is the core reason to use Ansible instead of shell scripts.
Installation
Install it only on the control node (your machine or management server that issues the work).
# Ubuntu / Debian
sudo apt update && sudo apt install -y ansible
# RHEL / CentOS / Rocky
sudo dnf install -y ansible-core
# pip ( when)
python3 -m pip install --user ansible
ansible --versionTarget servers only need SSH access and Python 3.
Inventory — Defining What You Manage
An inventory is the file that records which servers you manage and how you group them.
# inventory.ini
[web]
web1 ansible_host=10.0.0.11
web2 ansible_host=10.0.0.12
[db]
db1 ansible_host=10.0.0.21
[prod:children] # web + db
web
db
[all:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/deploy.pemFirst, verify connectivity with the ping module (this is not ICMP — it checks SSH + Python).
ansible -i inventory.ini all -m ping
# web1 | SUCCESS => { "ping": "pong" }Ad-hoc Commands — One-Liners Without a Playbook
For simple one-off tasks, run them immediately with -m (module) and -a (arguments).
# uptime check
ansible -i inventory.ini all -a "uptime"
# web ( -b = become)
ansible -i inventory.ini web -b -m apt -a "name=nginx state=present"
# when
ansible -i inventory.ini web -b -m service -a "name=nginx state=restarted"Playbooks — Declaring Work in a File
A playbook is YAML that defines multiple tasks in order.
# site.yml
- name:
hosts: web
become: true # sudo run
vars:
doc_root: /var/www/app
tasks:
- name: Nginx
apt:
name: nginx
state: present
update_cache: true
- name:
file:
path: "{{ doc_root }}"
state: directory
owner: www-data
mode: "0755"
- name: settings file
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/app.conf
notify: reload nginx # when call
handlers:
- name: reload nginx
service:
name: nginx
state: reloadedMake syntax checks and dry-runs (change previews) a habit before you apply.
ansible-playbook -i inventory.ini site.yml --syntax-check
ansible-playbook -i inventory.ini site.yml --check --diff #
ansible-playbook -i inventory.ini site.yml #Commonly Used Modules
| Module | Purpose |
|---|---|
apt / dnf / package | Install or remove packages |
service / systemd | Start, stop, or enable services |
copy / template | Deploy files (static / Jinja2 templates) |
file | Directories, permissions, symbolic links |
lineinfile / blockinfile | Edit part of a config file |
user / group | Account management |
git | Check out a repository |
command / shell | Arbitrary commands (not idempotent — minimize use) |
With command/shell, Ansible cannot determine state, so every run is reported as "changed." When you truly need them, grant idempotency yourself with creates=/removes= arguments or changed_when.
Handlers and Idempotency
Handlers are special tasks that run only when a change actually occurred. Use them for the pattern of reloading a service only when a config file changed. You invoke them with notify; even if multiple tasks notify the same handler, it runs only once at the end of the play.
If you run the same playbook twice, the second run should mostly show changed=0. That is the signal of a properly idempotent playbook.
Roles — Split into Reusable Structure
As a project grows, split work into roles. Follow the standard directory layout and Ansible will pick up the files automatically.
roles/
nginx/
tasks/main.yml #
handlers/main.yml #
templates/ # Jinja2
defaults/main.yml #
vars/main.yml #ansible-galaxy init roles/nginx ## site.yml —
- hosts: web
become: true
roles:
- nginx
- { role: app, tags: ["deploy"] }Practical Tips
- Narrow the scope with
--limit:ansible-playbook site.yml --limit web1— validate on a single server first. --tags/--skip-tags: In a large playbook, run only the deploy portion.- Manage secrets with Vault: Encrypt passwords and keys with
ansible-vault encrypt secrets.ymland decrypt with--ask-vault-pass. - Pin defaults in
ansible.cfg: So you do not type the inventory path, become setting, and SSH options every time.
# ansible.cfg
[defaults]
inventory = ./inventory.ini
host_key_checking = False
retry_files_enabled = FalseAnsible becomes far more powerful when you shift your thinking from "a script to run" to "a declaration of desired state." Start with small ad-hoc commands, promote repeating work into playbooks, then promote reusable pieces into roles — that sequence lets you scale naturally.
AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us
Questions & answers
Ask a question about this guide. We'll review and reply.