Installation and Your First Ad-hoc Commands
This article sets up the practice environment and runs the first Ansible commands. By the end you'll be able to drive a remote server with ad-hoc commands — quickly running a single module, no playbook needed yet.
The series' hands-on code is at github.com/nghiadaulau/ansible-series. Clone it to follow along.
Install Ansible (control node)
Ansible only needs to be installed on one control machine (recall Article 1 — agentless). Install it with pip (the most common way, always the latest):
pip install ansible # full edition (includes collections)
# or leaner: pip install ansible-core
ansible --version # check
ansible [core 2.19.x]
python version = 3.11...
(Alternatives: brew install ansible on macOS, or your distro's package manager — recall Linux series Article 11. On Windows, install inside WSL since Ansible doesn't run natively on Windows.)
Prepare a host to manage
We need a target machine. This article uses an EC2 Amazon Linux instance (recall AWS series Article 2 for how to create one) — but any Linux machine you can SSH into works. The host's requirements (Article 1): SSH-able + has Python (Amazon Linux 2023 ships python3).
You'll need: the host's public IP address, the login user (ec2-user for Amazon Linux), and the SSH key (a .pem file). Remember to tighten the Security Group to open SSH only to your IP (AWS series Article 3).
Configuration: ansible.cfg and inventory
Create a project directory, then two files.
ansible.cfg — the default configuration for this directory:
[defaults]
inventory = inventory.ini
host_key_checking = False
remote_user = ec2-user
private_key_file = ansible-lab.pem
interpreter_python = /usr/bin/python3
inventory.ini — the host list (Article 3 goes deeper):
[web]
lab ansible_host=203.0.113.10
Here lab is a friendly name for the host, and [web] is a group. host_key_checking = False is handy for a lab (skips the host-key confirmation prompt). Don't commit the .pem file to Git — add it to .gitignore.
Ad-hoc commands: run a single module quickly
An ad-hoc command has the structure:
ansible <pattern> -m <module> -a "<parameters>"
└───┬───┘ └────┬───┘ └─────┬──────┘
host/group module parameters
<pattern> is the host/group to apply to (all, web, lab...). This is how you quickly try out an operation without a playbook.
ping: check connectivity
ansible all -m ping
lab | SUCCESS => {
"changed": false,
"ping": "pong"
}
pong means Ansible SSH'd in, found Python, and ran the module successfully (the exact lifecycle from Article 1). "changed": false because ping changes nothing — note this changed field, it's the spirit of idempotency (Article 5).
Note: Ansible's
pingmodule is not the ICMPpingcommand. It checks "SSH + Python + module runs" — i.e. it verifies Ansible can manage the host.
command and shell: run commands
ansible all -m command -a "uptime"
lab | CHANGED | rc=0 >>
10:16:19 up 3 min, 1 user, load average: 0.29, 0.12
command is the default module (ansible all -a "uptime" also works). But command does not support pipes, redirects, or shell variables. When you need those, use shell:
ansible all -m shell -a "cat /etc/os-release | grep PRETTY_NAME"
lab | CHANGED | rc=0 >>
PRETTY_NAME="Amazon Linux 2023..."
Rule: prefer
command(safer, doesn't go through a shell); only useshellwhen you need pipes/redirects. And even better: use a dedicated module (apt, copy, service...) instead of command/shell whenever one exists — because they're idempotent, whereascommand/shellalways reportCHANGED(Ansible can't tell whether an arbitrary command changed anything).
become: run as root
Many tasks need sudo (recall Linux series Article 12). Add -b (become):
ansible all -b -m command -a "whoami"
lab | CHANGED | rc=0 >>
root
-b elevates to root (via sudo) to run. You'll use become for nearly every admin task (installing packages, editing system files).
setup: gather facts
The setup module gathers facts — information about the host (OS, IP, RAM, CPU...):
ansible all -m setup -a "filter=ansible_distribution*"
"ansible_distribution": "Amazon",
"ansible_distribution_version": "2023",
Facts are extremely useful: you can write tasks that vary by OS, version, and so on. We dig into facts in Article 6.
When to use ad-hoc, when to use a playbook
Ad-hoc fits one-off, quick jobs: checking state, restarting a single service across many machines, viewing facts. For repeatable, multi-step, save-worthy work, write a playbook (Article 4) — that's where Ansible's real power lies.
🧹 Note
The lab EC2 stays around throughout the series for practice; we terminate it in Article 16. If you want to stop partway through, remember to terminate it to avoid running up a bill (AWS series Article 2).
Wrap-up
Install Ansible on the control node (pip/brew/package), prepare a host that's SSH-able + has Python, then declare it in ansible.cfg and inventory.ini. An ad-hoc command (ansible <pattern> -m <module> -a "<args>") quickly runs one module: ping (verify the host is manageable), command/shell (run a command — prefer command), -b/become (root), setup (facts). Prefer dedicated modules because they're idempotent. Ad-hoc is for one-offs; for repeatable work use a playbook.
Before writing a playbook, we need to understand the inventory well — how Ansible knows which machines to manage and how to group them. That's Article 3.