Overview
Ansible is an agentless configuration management tool. It connects to servers over SSH, runs tasks defined in YAML playbooks, and enforces the desired state of your infrastructure. This tutorial builds a playbook that installs Nginx, deploys a static site, and configures a firewall.
Why Ansible?
| Feature | Benefit |
|---|---|
| Agentless | No software to install on managed hosts; uses SSH |
| YAML syntax | Human-readable, order-independent at the task level |
| Idempotent | Re-running a playbook does not duplicate changes |
| Large module library | Built-in support for packages, files, services, cloud APIs |
Install Ansible
# Ubuntu
sudo apt update
sudo apt install ansible
# macOS
brew install ansible
# Via pip (any platform)
pip install ansible
See the Ansible installation guide for other platforms.
Core Concepts
| Term | Description |
|---|---|
| Inventory | List of managed hosts, grouped by role |
| Playbook | YAML file containing one or more plays |
| Play | Maps a group of hosts to a set of tasks |
| Task | A single action using a module |
| Module | Built-in unit of work, such as apt or copy |
| Role | Reusable bundle of tasks, files, and templates |
Step 1: Create an Inventory
Create inventory.ini:
[webservers]
web1.example.com
web2.example.com
[webservers:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519
Test connectivity:
ansible -i inventory.ini webservers -m ping
Step 2: Write the Playbook
Create site.yml:
---
- name: Configure web servers
hosts: webservers
become: true
vars:
site_root: /var/www/example
tasks:
- name: Update apt cache
apt:
update_cache: true
cache_valid_time: 3600
- name: Install Nginx
apt:
name: nginx
state: present
- name: Create site directory
file:
path: "{{ site_root }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Deploy index.html
copy:
src: files/index.html
dest: "{{ site_root }}/index.html"
owner: www-data
group: www-data
mode: '0644'
- name: Enable Nginx service
service:
name: nginx
state: started
enabled: true
- name: Allow HTTP through firewall
ufw:
rule: allow
port: '80'
proto: tcp
Step 3: Run the Playbook
ansible-playbook -i inventory.ini site.yml
Add --check to preview changes, or --diff to see file differences:
ansible-playbook -i inventory.ini site.yml --check --diff
Common Modules Reference
| Module | Purpose |
|---|---|
apt / yum | Install or remove packages |
copy | Copy files to managed hosts |
template | Render Jinja2 templates |
file | Manage files, directories, and symlinks |
service / systemd | Manage services |
user / group | Manage accounts |
git | Clone or update repositories |
command / shell | Run arbitrary commands |
Using Variables and Handlers
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
tasks:
- name: Update Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
A handler runs only when a task notifies it and reports a change.
Best Practices
- Store playbooks in Git and run them through CI.
- Use roles for anything reused across projects.
- Prefer
--checkmode before applying to production. - Encrypt secrets with
ansible-vault. - Keep inventory separate from playbooks so environments can share code.
