The Ansible commands you’ll use most are ansible for ad hoc tasks, ansible-playbook to run playbooks, ansible-inventory to check your hosts, ansible-doc to read module documentation, ansible-vault for secrets and ansible-galaxy for collections. Before any change, run --syntax-check, then --check --diff, then limit the run with --limit. This free 48-page PDF covers those commands plus modules, variables, patching and RHEL Leapp upgrades. This page shows the ones to learn first.
Ansible commands list: the seven command-line tools
Seven tools cover most daily work, and each does one job. Run ansible --version first so you know which release you’re on.
| Command | What it does | Example |
|---|---|---|
ansible | Runs one module on a set of hosts (ad hoc) | ansible all -i inventory.ini -m ansible.builtin.ping |
ansible-playbook | Runs a playbook | ansible-playbook site.yml --check --diff |
ansible-inventory | Shows how Ansible reads your inventory | ansible-inventory -i inventory.ini --graph |
ansible-doc | Shows module and plugin documentation | ansible-doc ansible.builtin.copy |
ansible-vault | Encrypts and edits secret files | ansible-vault create group_vars/prod/vault.yml |
ansible-galaxy | Installs collections and roles | ansible-galaxy collection install -r requirements.yml |
ansible-config | Shows which settings are active | ansible-config dump --only-changed |
Ansible’s own CLI cheat sheet lists more flags for each tool. The PDF puts the same commands in the order you’d use them on a real server.
Ansible ad hoc commands: run one task on many hosts
An ad hoc command runs one module on one or more hosts without a playbook. The pattern is ansible <hosts> -i <inventory> -m <module> -a "<arguments>". According to Ansible’s ad hoc documentation, ad hoc commands are quick but not reusable, so move anything you repeat into a playbook.
ansible all -i inventory.ini -m ansible.builtin.ping
ansible web -i inventory.ini -m ansible.builtin.setup
ansible localhost -m ansible.builtin.apt -a "name=apache2 state=present" -b -K
The first line tests that Ansible can log in and run Python on every host. The second collects facts from the web group. The third installs a package: -b escalates privilege and -K asks for the sudo password.
Ad hoc commands answer “is it up?” and “what’s on it?”. Playbooks answer “make it look like this”. Not sure whether to learn Ansible or Terraform first? Read Terraform vs Ansible: which one to learn first.
Ansible inventory commands: check your hosts before you run anything
Inventory tells Ansible who to talk to. Check how Ansible reads it before every run with ansible-inventory --graph.
# inventory.ini
[web]
web01 ansible_host=10.10.10.11
web02 ansible_host=10.10.10.12
[db]
db01 ansible_host=10.10.20.11
[prod:children]
web
db
[all:vars]
ansible_user=automation
ansible-inventory -i inventory.ini --graph
ansible-inventory -i inventory.ini --list
--graph shows your groups as a tree. --list prints the full inventory as JSON, including host variables. A static file is fine for a lab. Once your hosts change every week, switch to an inventory plugin instead of editing text. Our NetBox dynamic inventory for Ansible guide shows one that stays accurate.
Ansible playbook commands: run, check and limit safely
Run every playbook in the same order: syntax check, dry run with a diff, one host, then the full run. These flags come straight from the PDF’s safe-execution section.
| Command | What it does |
|---|---|
ansible-playbook site.yml --syntax-check | Catches YAML and structure errors before anything runs |
ansible-playbook site.yml --list-hosts | Shows which hosts the play would touch |
ansible-playbook site.yml --list-tasks | Lists the tasks in order |
ansible-playbook site.yml --check --diff | Dry run, and shows the file changes it would make |
ansible-playbook site.yml --limit web01 | Runs on one host or group only |
ansible-playbook site.yml --tags patch | Runs only tasks with that tag |
ansible-playbook site.yml --skip-tags reboot | Skips tasks with that tag |
ansible-playbook site.yml --start-at-task "Validate service" | Resumes at a named task |
ansible-playbook site.yml -vv | Adds detail when a task fails |
A minimal playbook looks like this. Give every task a name, use fully qualified collection names such as ansible.builtin.package, and keep one job per task.
---
- name: Baseline Linux configuration
hosts: linux
become: true
gather_facts: true
vars:
package_name: chrony
tasks:
- name: Install chrony
ansible.builtin.package:
name: "{{ package_name }}"
state: present
- name: Enable and start the service
ansible.builtin.service:
name: chronyd
state: started
enabled: true
Tags let you run a slice of a big playbook:
- name: Patch operating system
ansible.builtin.dnf:
name: "*"
state: latest
tags: [patch]
- name: Reboot if required
ansible.builtin.reboot:
reboot_timeout: 900
tags: [reboot]
Check mode is an aid, not a guarantee. Not every module can predict its changes, so read the diff and don’t treat a clean dry run as proof. Ansible’s check mode documentation explains where it falls short.

What does idempotent mean in Ansible?
An idempotent task gives the same result whether you run it once or ten times. If the system already matches what you asked for, Ansible reports ok and changes nothing. Modules like package, file and service work this way. command and shell don’t, because Ansible can’t tell what a command did, so you add a guard.
- name: Run a one-time migration
ansible.builtin.command: /usr/local/bin/migrate-db
args:
creates: /var/lib/myapp/.migration-v2-complete
- name: Check application health
ansible.builtin.command: /usr/local/bin/healthcheck
register: health
changed_when: false
failed_when:
- health.rc != 0
- "'WARNING' not in health.stdout"
creates skips the task when the file already exists. changed_when: false stops a read-only command from reporting a change. failed_when lets you decide what counts as a failure. A task that reports changed on every run is the first sign of a missing guard. Interviewers ask about this all the time, and our 120 Ansible interview questions answer it precisely.
Ansible command vs shell: which module should you use?
Use command unless you need shell features such as pipes or redirection. It runs the program directly, so there’s no shell parsing and fewer surprises. shell runs through a shell, so pipes work, but you must add set -o pipefail and a changed_when yourself. raw skips the normal module transport and is mainly for bootstrapping.
- name: Run a command without a shell
ansible.builtin.command:
argv:
- /usr/bin/ss
- -lntup
register: sockets
changed_when: false
- name: Use shell only when shell features are required
ansible.builtin.shell: |
set -o pipefail
rpm -qa | sort | head -20
args:
executable: /bin/bash
register: rpm_sample
changed_when: false
Both modules have full reference pages in Ansible’s docs: ansible.builtin.command and ansible.builtin.shell.

Go deeper
Reading a playbook isn’t the same as running one.
This PDF shows you the commands. The DevOps Certification Workbook has you run them, with Ansible fundamentals, playbooks and roles, and Ansible Vault sitting next to Git, CI/CD, Docker, Kubernetes and Terraform. Start a lab, break it, fix it, and the flags stop being trivia.
See the DevOps Certification Workbook →ansible-doc command: find any module or plugin from the terminal
ansible-doc shows the documentation for the Ansible you have installed, so the answer always matches your version. Start with ansible-doc -l.
ansible-doc -l
ansible-doc ansible.builtin.copy
ansible-doc ansible.builtin.package
ansible-doc -t filter -l
ansible-doc -t test -l
ansible-doc -t lookup -l
ansible-doc -t connection -l
ansible-doc -t become -l
ansible-galaxy collection list
ansible-config dump --only-changed
-t picks the plugin type: filter, test, lookup, connection, become, callback, strategy, inventory or vars. ansible-galaxy collection list shows which collections are installed, and ansible-config dump --only-changed shows the settings you’ve changed. The full option list is in the ansible-doc reference.
Ansible Vault commands: create, edit and view encrypted files
Ansible Vault encrypts variable files so secrets can sit next to your playbooks without being readable. The PDF covers create, edit and view, plus the flag that asks for the password at run time.
ansible-vault create group_vars/prod/vault.yml
ansible-vault edit group_vars/prod/vault.yml
ansible-vault view group_vars/prod/vault.yml
ansible-playbook site.yml --ask-vault-pass
According to Ansible’s ansible-vault reference, the same tool also has encrypt, decrypt, encrypt_string and rekey. You can pass the password with --ask-vault-pass, --vault-password-file or --vault-id.
- name: Configure application secret
ansible.builtin.template:
src: app-secret.conf.j2
dest: /etc/myapp/secret.conf
mode: "0600"
no_log: true
Three rules. Never put plaintext production secrets in Git. Add no_log: true to any task that might print one. And remember that Vault encrypts Ansible data, but it doesn’t protect a secret you pass on a command line.
Ansible modules cheat sheet: where each module lives
A module name that works on one control node fails on another when its collection isn’t installed. You’ll see a “couldn’t resolve module/action” error. Check where a module lives before you copy it.
| Collection | Modules you’ll use | Install needed? |
|---|---|---|
ansible.builtin | file, copy, template, lineinfile, blockinfile, dnf, apt, package, service, systemd, user, group, command, shell, reboot, setup, stat, find, unarchive, uri, get_url, wait_for, cron, hostname | No, ships with ansible-core |
ansible.posix | authorized_key, acl, mount, sysctl, firewalld | Yes |
community.general | filesystem, lvol, lvg, parted, timezone, archive | Yes |
List the collections you depend on in requirements.yml, so CI and your colleagues build the same environment:
# requirements.yml
collections:
- name: ansible.posix
- name: community.general
- name: redhat.leapp
ansible-galaxy collection install -r requirements.yml
ansible-galaxy is the command that installs collections and roles. Ansible Galaxy is the public hub where they’re shared. Verify any module with ansible-doc first.
The file module sets owner, group and mode, so keep our chmod, chown and umask cheat sheet next to you. New to the Linux side? The Linux commands cheat sheet covers the shell commands behind these modules.
- name: Create application directory
ansible.builtin.file:
path: /opt/myapp
state: directory
owner: appuser
group: appuser
mode: "0750"
Want these modules in a lab, not a table? The DevOps Workbook lists Ansible fundamentals, playbooks and roles, and Ansible Vault in its Infrastructure as Code and Cloud DevOps chapter.

Ansible facts, variables, when and loop
Facts are information Ansible gathers about each host: OS family, kernel, memory and interfaces. Read them through the ansible_facts dictionary.
- name: Show selected facts
hosts: linux
gather_facts: true
tasks:
- name: Print selected facts
ansible.builtin.debug:
msg:
hostname: "{{ ansible_facts['hostname'] }}"
os: "{{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"
kernel: "{{ ansible_facts['kernel'] }}"
memory_mb: "{{ ansible_facts['memtotal_mb'] }}"
Older playbooks use injected names such as ansible_hostname. According to Ansible’s ansible-core 2.20 porting guide, those are deprecated and the default switches in 2.24, so write ansible_facts['hostname'] in new code.
Conditionals and loops use Jinja expressions. Don’t wrap a when expression in {{ }}.
- name: Restart only on RHEL 9
ansible.builtin.service:
name: myapp
state: restarted
when:
- ansible_facts['os_family'] == "RedHat"
- ansible_facts['distribution_major_version'] | int == 9
- name: Install multiple packages
ansible.builtin.package:
name: "{{ item }}"
state: present
loop:
- curl
- rsync
- tar
Group risky steps in block, rescue and always so a failure runs your recovery task and a final status check either way.
Ansible handlers and Jinja2 templates: restart only when something changed
A handler runs when a task notifies it, and by default at the end of the play. Use one for every restart that depends on a config change.
tasks:
- name: Deploy configuration
ansible.builtin.template:
src: myapp.conf.j2
dest: /etc/myapp.conf
notify: Restart myapp
handlers:
- name: Restart myapp
ansible.builtin.systemd:
name: myapp
state: restarted
The template renders variables and logic before the file is copied:
# templates/myapp.conf.j2
server_name = {{ inventory_hostname }}
listen_port = {{ app_port }}
{% for backend in backends %}
backend = {{ backend }}
{% endfor %}
Keep complex logic out of templates when you can compute it in vars or set_fact, and use to_json or to_yaml when you embed structured data.
Ansible patching playbook for RHEL, Rocky, Alma and Ubuntu
One playbook can patch both families if you branch on os_family and limit the batch with serial.
- name: Linux patch baseline
hosts: linux
become: true
serial: 10
tasks:
- name: Update RHEL-family hosts
ansible.builtin.dnf:
name: "*"
state: latest
update_cache: true
when: ansible_facts['os_family'] == "RedHat"
- name: Update Debian-family hosts
ansible.builtin.apt:
name: "*"
state: latest
update_cache: true
when: ansible_facts['os_family'] == "Debian"
Then decide on a reboot. On RHEL-family systems, needs-restarting -r returns a non-zero code when one is needed, so register the result and test it. The command has to be installed on the host.
- name: Check whether a reboot is required (RHEL family)
ansible.builtin.command: needs-restarting -r
register: reboot_check
changed_when: false
failed_when: false
when: ansible_facts['os_family'] == "RedHat"
- name: Reboot if required
ansible.builtin.reboot:
reboot_timeout: 1200
when:
- ansible_facts['os_family'] == "RedHat"
- reboot_check.rc | default(0) != 0
After the reboot, prove the service came back instead of assuming it did:
- name: Collect service state
ansible.builtin.service_facts:
- name: Assert application service is running
ansible.builtin.assert:
that:
- ansible_facts.services['myapp.service'].state == 'running'
fail_msg: "myapp is not running after reboot"
A full patch workflow has prechecks, a repository health check, a disk space check, a drain or maintenance step, the update, the reboot decision, service validation and post-checks.
RHEL Leapp upgrade with Ansible: analyse first, upgrade last
An in-place RHEL major upgrade is a different job from patching. Run leapp preupgrade, read the report, fix every inhibitor, then upgrade in a change window with an approval step.
leapp preupgrade --target-version 9.8
less /var/log/leapp/leapp-report.txt
Replace the target with a path Red Hat supports for your source release. Red Hat ships the redhat.leapp collection with analysis, remediate and upgrade roles. According to Red Hat’s upgrade documentation, the control node for these roles must run RHEL 9 or RHEL 10.
- name: RHEL pre-upgrade analysis using the Red Hat Leapp role
hosts: rhel8
become: true
tasks:
- name: Generate pre-upgrade analysis
ansible.builtin.import_role:
name: redhat.leapp.analysis
vars:
leapp_upgrade_type: cdn
Don’t continue automatically from the analysis to the upgrade. Review the report, resolve the inhibitors, and confirm the exact source and target path first. Don’t run configuration or deployment tasks during the upgrade phase either.
Ansible roles, collections and project layout
A role is a reusable folder of tasks, handlers, templates, files and defaults. A collection packages roles, modules and plugins. Keep production and non-production inventories apart, pin your collection versions, and put the guardrails in the playbook: assertions, serial, tags and explicit limits.
ansible-project/
ansible.cfg
requirements.yml
inventories/
production/
nonprod/
playbooks/
site.yml
patch.yml
roles/
baseline/
collections/
Two settings matter early:
# ansible.cfg
[defaults]
inventory = inventory.ini
forks = 20
timeout = 30
[ssh_connection]
pipelining = true
forks sets how many hosts run at once. Don’t raise it blindly: your network, package repositories and application capacity become the bottleneck.
Ansible troubleshooting: symptom, likely cause, first check
| Symptom | Likely area | First checks |
|---|---|---|
| UNREACHABLE | SSH, network or auth | ssh -v, inventory, DNS, route, key, user |
| Permission denied | sudo, become or file modes | become, sudoers, file ownership and mode |
| Module not found | Collection or version | ansible-doc, ansible-galaxy collection list |
| Task changed every run | Idempotence | Module choice, changed_when, command side effects |
| YAML error | Syntax or indentation | ansible-playbook site.yml --syntax-check |
| Fact missing | Fact gathering or platform | setup, gather_subset, exact fact name |
| Template error | Jinja or undefined variable | -vvv, assert the variable is defined |
| Timeout | Network or application | timeout, wait_for, async and poll |
For an UNREACHABLE host, rule out the network before you blame Ansible. Our network troubleshooting commands cheat sheet covers ping, traceroute and the checks around them.
Where does this free Ansible PDF stop?
The PDF is a reference. You can read every playbook in it, but nothing breaks while you watch. The DevOps Certification Workbook pairs its Ansible chapters with ready-to-use Git repositories, Docker Compose files and Helm charts, so you can start a first lab in under ten minutes. Preview its sample pages on the workbook page before you decide.
What to read next:
- Terraform is the usual second tool. Keep the Terraform commands cheat sheet open while you practise.
- Heading into an interview? The DevOps interview questions PDF has 180 questions with answers across the toolchain.
- Learning Ansible for a network role? See network automation engineer skills and pay for what employers ask for.
