Ansible Getting Started: Inventory, Playbooks, and Ad-Hoc Commands¶
Ansible is how I manage infrastructure at scale — or even just across a handful of machines. You write what you want the end state to look like, Ansible figures out how to get there. No agents needed on the remote machines, just SSH.
The Short Answer¶
# Install Ansible
pip install ansible
# Run a one-off command on all hosts
ansible all -i inventory.ini -m ping
# Run a playbook
ansible-playbook -i inventory.ini site.yml
Core Concepts¶
Inventory — the list of machines Ansible manages. Can be a static file or dynamically generated.
Playbook — a YAML file describing tasks to run on hosts. The main thing you write.
Module — the building blocks of tasks. apt, dnf, service, copy, template, user, etc. Ansible has modules for almost everything.
Idempotency — run the same playbook ten times, the result is the same as running it once. Ansible modules are designed this way. This matters because it means you can re-run playbooks safely without side effects.
Inventory File¶
# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_user=admin
[databases]
db1.example.com ansible_user=ubuntu ansible_port=2222
[all:vars]
ansible_user=myuser
ansible_ssh_private_key_file=~/.ssh/id_ed25519
Test connectivity:
A successful response looks like:
Ad-Hoc Commands¶
For quick one-offs without writing a playbook:
# Run a shell command
ansible all -i inventory.ini -m shell -a "uptime"
# Install a package
ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" --become
# Restart a service
ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" --become
# Copy a file
ansible all -i inventory.ini -m copy -a "src=./myfile dest=/tmp/myfile"
--become escalates to sudo.
Writing a Playbook¶
---
# site.yml
- name: Configure web servers
hosts: webservers
become: true
vars:
app_port: 8080
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Start and enable nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Deploy config file
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Run it:
ansible-playbook -i inventory.ini site.yml
# Dry run — shows what would change without doing it
ansible-playbook -i inventory.ini site.yml --check
# Verbose output
ansible-playbook -i inventory.ini site.yml -v
Handlers¶
Handlers run at the end of a play, only if notified. The canonical use is "reload service after config change":
tasks:
- name: Deploy config
ansible.builtin.template:
src: templates/app.conf.j2
dest: /etc/app/app.conf
notify: Restart app
handlers:
- name: Restart app
ansible.builtin.service:
name: myapp
state: restarted
If the config file didn't change (idempotent — it was already in the right state), the notify never fires and the service isn't restarted.
Roles¶
Once playbooks get complex, organize them into roles:
Use a role in a playbook:
Roles keep things organized and reusable across projects.
Gotchas & Notes¶
- YAML indentation matters. Two spaces is standard. Tab characters will break your playbooks.
--checkis your friend. Always dry-run against production before applying changes.- SSH key access is required. Ansible connects over SSH — password auth works but key auth is what you want for automation.
gather_facts: falsespeeds up playbooks when you don't need host facts (OS, IP, etc.). Add it at the play level for simple playbooks.- Ansible is not idempotent by magic. Shell and command modules run every time regardless of state. Use the appropriate module (
apt,service,file, etc.) instead ofshellwhenever possible. - The
ansible-linttool catches common mistakes before they run. Worth adding to your workflow.
See Also¶
- [[managing-linux-services-systemd-ansible]]
- [[linux-server-hardening-checklist]]