Introduction
Ansible automates server management: installing software, deploying configs, managing services, and more — all without agents. You write what you want in YAML, and Ansible makes it happen over SSH. This guide takes you from zero to your first working playbook.
What Is Ansible?
Ansible is an automation tool that:
- Connects to servers via SSH (no agent needed)
- Reads your instructions from YAML files (playbooks)
- Executes modules on remote servers to achieve desired state
- Reports what changed and what was already correct
Your laptop SSH Remote servers
┌──────────┐ ──────────> ┌──────────────┐
│ Ansible │ │ web-server-1 │
│ Playbook │ ──────────> │ web-server-2 │
│ │ │ db-server-1 │
└──────────┘ ──────────> └──────────────┘
Control node Managed nodes
Install Ansible
# macOS
brew install ansible
# Ubuntu/Debian
sudo apt update
sudo apt install ansible
# RHEL/Fedora
sudo dnf install ansible
# pip (any OS — recommended for latest version)
pip install ansible
# Verify installation
ansible --version
# ansible [core 2.20.0]
Your First Command (Ad-Hoc)
# Ping a server (test connection)
ansible all -i "server1.example.com," -m ping
# Note: comma after hostname is required for single-host inventory
# Run a command
ansible all -i "server1.example.com," -m command -a "uptime"
# Install a package
ansible all -i "server1.example.com," -m apt -a "name=nginx state=present" --become
Inventory — Define Your Servers
# inventory.yml
all:
children:
webservers:
hosts:
web1:
ansible_host: 192.168.1.10
web2:
ansible_host: 192.168.1.11
dbservers:
hosts:
db1:
ansible_host: 192.168.1.20
vars:
ansible_user: deploy
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
# List hosts in inventory
ansible-inventory -i inventory.yml --list
# Ping all webservers
ansible webservers -i inventory.yml -m ping
Your First Playbook
# setup-webserver.yml
---
- name: Set up web servers
hosts: webservers
become: true # Run as root
vars:
app_port: 8080
packages:
- nginx
- python3
- curl
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install required packages
ansible.builtin.apt:
name: "{{ packages }}"
state: present
- name: Deploy nginx configuration
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: Restart nginx
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Verify nginx is responding
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}/index.html"
register: result
until: result.status == 200
retries: 5
delay: 2
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
# Run the playbook
ansible-playbook -i inventory.yml setup-webserver.yml
# Dry run first (preview changes)
ansible-playbook -i inventory.yml setup-webserver.yml --check --diff
Key Concepts
Modules — What Ansible Can Do
# File management
- ansible.builtin.file: # Create/delete files and directories
- ansible.builtin.copy: # Copy files to remote
- ansible.builtin.template: # Copy with variable substitution
# Package management
- ansible.builtin.apt: # Debian/Ubuntu packages
- ansible.builtin.dnf: # RHEL/Fedora packages
# Service management
- ansible.builtin.service: # Start/stop/restart services
- ansible.builtin.systemd: # Systemd-specific operations
# User management
- ansible.builtin.user: # Create/modify users
- ansible.builtin.group: # Create/modify groups
# Commands
- ansible.builtin.command: # Run commands (safe)
- ansible.builtin.shell: # Run commands (with pipes/redirects)
Variables
# In playbook
vars:
app_name: myapp
# In separate file
vars_files:
- vars/common.yml
# From command line
# ansible-playbook site.yml -e "app_name=myapp"
# Use in tasks
- name: Deploy {{ app_name }}
ansible.builtin.copy:
src: "{{ app_name }}.conf"
dest: "/etc/{{ app_name }}/config.yml"
Conditionals
# Run task only when condition is true
- name: Install on Ubuntu only
ansible.builtin.apt:
name: nginx
when: ansible_distribution == "Ubuntu"
- name: Restart if config changed
ansible.builtin.service:
name: myapp
state: restarted
when: config_result is changed
Loops
# Iterate over a list
- name: Create users
ansible.builtin.user:
name: "{{ item }}"
state: present
loop:
- alice
- bob
- charlie
Handlers — React to Changes
tasks:
- name: Update config
ansible.builtin.template:
src: app.conf.j2
dest: /etc/app/config.yml
notify: Restart app
# Handler only runs if this task makes a change
handlers:
- name: Restart app
ansible.builtin.service:
name: myapp
state: restarted
Roles — Reusable Automation
# Create a role skeleton
ansible-galaxy role init roles/webserver
# Use roles in a playbook
- hosts: webservers
roles:
- common
- nginx
- role: app_deploy
vars:
app_version: "2.0"
Common First-Time Issues
# Problem: "Permission denied"
# Fix: Add --become (or become: true in playbook)
ansible-playbook site.yml --become
# Problem: "Host key verification failed"
# Fix: Add to ansible.cfg
[defaults]
host_key_checking = false
# Problem: "No hosts matched"
# Fix: Check inventory file and host patterns
ansible-inventory -i inventory.yml --graph
# Problem: "MODULE FAILURE" on command
# Fix: Use FQCN and check module parameters
# Bad: apt: pkg=nginx
# Good: ansible.builtin.apt: name=nginx state=present
Next Steps
- Learn roles — Package your playbooks into reusable components
- Use Ansible Vault — Encrypt passwords and secrets
- Set up inventory per environment — staging, production
- Add to CI/CD — Automate deployments on git push
- Explore collections — Community modules for AWS, Docker, Kubernetes
Recommended Reading Order
- Ansible Playbook Guide
- Ansible Inventory Guide
- Ansible Roles Guide
- Ansible Variables Guide
- Ansible Vault Guide
- Ansible Best Practices
Conclusion
Ansible uses SSH to connect, YAML to describe desired state, and modules to make it happen. Start with ad-hoc commands to test connectivity, then write playbooks for repeatable automation. Use --check --diff to preview before applying. As your project grows, organize with roles and manage secrets with Vault. The learning curve is gentle — if you can write YAML, you can write Ansible.