What Is Ansible and Why Should You Care?
Imagine you manage 10 servers. A security patch needs to be applied to all of them. You SSH into each one, run the same commands, and hope you do not miss a step or make a typo on server number seven. Now imagine you manage 50 servers. Or 200. Manual server configuration does not scale, and it is frighteningly error-prone.
Ansible is an open-source automation tool that lets you describe your server configuration in simple, human-readable YAML files and apply those configurations across hundreds of servers with a single command. Unlike Puppet or Chef, Ansible is agentless — it connects to your servers over SSH and runs commands. There is nothing to install on the managed servers, which makes getting started incredibly fast.
By the end of this guide, you will have Ansible installed, understand inventory files, write your first playbook, and automate a complete LAMP stack deployment. All in about 30 minutes of reading and practicing.
Installing Ansible
Ansible runs on your control machine (your laptop or a management server) and connects to managed nodes via SSH. You only need to install Ansible on the control machine.
$ sudo apt update
$ sudo apt install -y ansible
# macOS (with Homebrew)
$ brew install ansible
# pip (any platform)
$ pip install ansible
# Verify installation
$ ansible --version
ansible [core 2.17.0]
python version = 3.12.3
The Inventory File: Defining Your Servers
The inventory file tells Ansible which servers to manage. You can group servers by role, environment, or any other criteria that makes sense for your infrastructure.
INI Format (Simple)
[webservers]
web1.example.com
web2.example.com
192.168.1.50 ansible_port=2222
[dbservers]
db1.example.com ansible_user=dbadmin
db2.example.com
[production:children]
webservers
dbservers
[all:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/deploy_key
YAML Format (Structured)
all:
vars:
ansible_user: deploy
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
dbservers:
hosts:
db1.example.com:
ansible_user: dbadmin
Ad-Hoc Commands: Quick Tasks Without Playbooks
Before writing playbooks, you can run one-off commands across your servers using ad-hoc commands. These are perfect for quick tasks like checking connectivity, gathering information, or making simple changes.
$ ansible all -i inventory.ini -m ping
web1.example.com | SUCCESS => {"ping": "pong"}
web2.example.com | SUCCESS => {"ping": "pong"}
db1.example.com | SUCCESS => {"ping": "pong"}
# Check disk space on web servers
$ ansible webservers -i inventory.ini -m shell -a "df -h /"
# Check uptime on all servers
$ ansible all -i inventory.ini -m command -a "uptime"
# Install a package on all web servers
$ ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" -b
# Restart a service
$ ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" -b
-b flag: The -b (or --become) flag tells Ansible to use privilege escalation (sudo) on the remote server. Most system administration tasks require root privileges, so you will use this flag frequently.
Your First Playbook
Playbooks are YAML files that describe a series of tasks to execute on your servers. They are the core of Ansible automation. Let us start with a simple playbook that installs and configures Nginx:
---
- name: Setup Nginx web server
hosts: webservers
become: true
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install Nginx
apt:
name: nginx
state: present
- name: Copy custom nginx config
copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Restart Nginx
- name: Ensure Nginx is running and enabled
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted
$ ansible-playbook -i inventory.ini setup-nginx.yml
PLAY [Setup Nginx web server] ************************************
TASK [Gathering Facts] *******************************************
ok: [web1.example.com]
ok: [web2.example.com]
TASK [Update apt cache] ******************************************
ok: [web1.example.com]
ok: [web2.example.com]
TASK [Install Nginx] *********************************************
changed: [web1.example.com]
changed: [web2.example.com]
PLAY RECAP *******************************************************
web1.example.com : ok=4 changed=2 unreachable=0 failed=0
web2.example.com : ok=4 changed=2 unreachable=0 failed=0
Understanding Idempotency
The most important concept in Ansible is idempotency. An idempotent task produces the same result whether you run it once or a hundred times. If Nginx is already installed, the apt module will report "ok" instead of "changed" and skip the installation. This means you can safely run your playbooks repeatedly without worrying about unintended side effects.
Essential Ansible Modules
Ansible ships with hundreds of modules for managing every aspect of your servers. Here are the ones you will use most often:
| Module | Purpose | Example |
|---|---|---|
apt / yum | Package management | apt: name=nginx state=present |
copy | Copy files to remote servers | copy: src=app.conf dest=/etc/nginx/ |
template | Copy with Jinja2 variable substitution | template: src=vhost.j2 dest=/etc/nginx/ |
service | Manage system services | service: name=nginx state=started |
file | Manage files and directories | file: path=/var/www state=directory |
user | Manage system users | user: name=deploy shell=/bin/bash |
lineinfile | Ensure a line exists in a file | lineinfile: path=/etc/ssh/sshd_config ... |
command | Run a command (not through shell) | command: /opt/app/migrate.sh |
shell | Run a command through shell | shell: cat /etc/passwd | grep deploy |
git | Clone or update a Git repository | git: repo=https://... dest=/opt/app |
Variables and Facts
Variables make your playbooks reusable across different environments. Facts are variables automatically gathered from your servers (OS version, IP addresses, memory, CPU count, etc.).
---
- name: Deploy application
hosts: webservers
become: true
vars:
app_name: mywebapp
app_port: 3000
app_user: deploy
node_version: "20"
tasks:
- name: Create app user
user:
name: "{{ app_user }}"
shell: /bin/bash
home: "/home/{{ app_user }}"
- name: Show server info (using facts)
debug:
msg: "Server {{ ansible_hostname }} runs {{ ansible_distribution }} {{ ansible_distribution_version }} with {{ ansible_memtotal_mb }}MB RAM"
Templates with Jinja2
Templates let you create configuration files with dynamic values. Ansible uses the Jinja2 templating engine, which supports variables, conditionals, loops, and filters.
server {
listen 80;
server_name {{ domain_name }};
root /var/www/{{ app_name }}/public;
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
{% if enable_ssl %}
listen 443 ssl;
ssl_certificate /etc/ssl/{{ domain_name }}.crt;
ssl_certificate_key /etc/ssl/{{ domain_name }}.key;
{% endif %}
}
- name: Configure Nginx virtual host
template:
src: templates/vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ domain_name }}.conf"
owner: root
group: root
mode: '0644'
notify: Reload Nginx
Roles: Organizing Your Playbooks
As your playbooks grow, you need a way to organize them. Roles provide a standardized directory structure for grouping tasks, templates, files, variables, and handlers.
nginx/
tasks/
main.yml Tasks to execute
handlers/
main.yml Service restart handlers
templates/
nginx.conf.j2 Jinja2 templates
files/
custom-error.html Static files
vars/
main.yml Role variables
defaults/
main.yml Default variables (overridable)
meta/
main.yml Role dependencies
---
- name: Setup web servers
hosts: webservers
become: true
roles:
- common
- nginx
- { role: php, php_version: "8.3" }
- mysql
Ansible Galaxy: Community Roles
Ansible Galaxy is a hub for sharing Ansible roles. Instead of writing everything from scratch, you can install community-maintained roles for common tasks:
$ ansible-galaxy install geerlingguy.nginx
$ ansible-galaxy install geerlingguy.mysql
$ ansible-galaxy install geerlingguy.php
# Or use a requirements file
$ cat requirements.yml
---
roles:
- name: geerlingguy.nginx
- name: geerlingguy.mysql
version: "4.0.0"
$ ansible-galaxy install -r requirements.yml
Error Handling
Not every task will succeed, and not every failure should stop the entire playbook. Ansible provides several mechanisms for handling errors gracefully.
- name: Check if app is running
command: pgrep myapp
register: app_status
ignore_errors: yes
# block/rescue: try-catch equivalent
- name: Deploy with rollback
block:
- name: Deploy new version
shell: ./deploy.sh
- name: Run health check
uri:
url: "http://localhost:3000/health"
status_code: 200
rescue:
- name: Rollback on failure
shell: ./rollback.sh
- name: Notify team
debug:
msg: "Deployment failed, rolled back!"
Practical Example: Complete LAMP Stack
Let us put everything together with a real-world example that deploys a complete LAMP (Linux, Apache/Nginx, MySQL, PHP) stack:
---
- name: Deploy LAMP Stack
hosts: webservers
become: true
vars:
php_version: "8.3"
mysql_root_password: "{{ vault_mysql_root_password }}"
app_domain: "example.com"
tasks:
- name: Install required packages
apt:
name:
- nginx
- "php{{ php_version }}-fpm"
- "php{{ php_version }}-mysql"
- "php{{ php_version }}-mbstring"
- mysql-server
- python3-mysqldb
state: present
update_cache: yes
- name: Configure Nginx vhost
template:
src: templates/vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ app_domain }}"
notify: Reload Nginx
- name: Enable site
file:
src: "/etc/nginx/sites-available/{{ app_domain }}"
dest: "/etc/nginx/sites-enabled/{{ app_domain }}"
state: link
- name: Start and enable services
service:
name: "{{ item }}"
state: started
enabled: yes
loop:
- nginx
- "php{{ php_version }}-fpm"
- mysql
handlers:
- name: Reload Nginx
service:
name: nginx
state: reloaded
Ansible vs Puppet vs Chef
| Feature | Ansible | Puppet | Chef |
|---|---|---|---|
| Architecture | Agentless (SSH) | Agent required | Agent required |
| Language | YAML (simple) | Puppet DSL | Ruby |
| Learning Curve | Low | Medium | High |
| Push/Pull | Push | Pull (with server) | Pull (with server) |
| Community | Largest | Large | Medium |
| Best For | General automation | Large enterprise | Developer-heavy orgs |
Quick Reference Checklist
- Ansible installed on your control machine
- SSH keys configured for passwordless access to managed servers
- Inventory file created with server groups defined
- Tested connectivity with
ansible all -m ping - First playbook written with proper YAML indentation
- Handlers used for service restarts triggered by config changes
- Variables used to make playbooks reusable
- Templates used for dynamic configuration files
- Roles used to organize complex playbooks
- Ansible Vault used for encrypting sensitive variables
What to Learn Next
You now have a solid foundation in Ansible. In 30 minutes, you have gone from zero to writing playbooks that can configure servers automatically. Here is where to go next: Ansible Vault for encrypting secrets, dynamic inventories for cloud environments (AWS, Azure, GCP), Ansible Tower/AWX for a web-based management UI, and writing custom modules in Python for specialized tasks.
The key to mastering Ansible is practice. Take a task you do manually on your servers today — installing packages, configuring services, deploying applications — and automate it with a playbook. Each playbook you write is one less manual process that can go wrong at 3 AM on a Saturday night.