Introduction
Ansible lets you:
- Configure servers in YAML (playbooks)
- Agentless: just SSH + Python on target
- Idempotent: replay a playbook 100 times, same result
- Modules for everything: apt, systemd, file, docker, k8s, postgres
- Reusable roles (Ansible Galaxy)
- Vault for secret encryption
Use cases: configure 50 identical servers, deploy an app, mass updates.
Prerequisites
- Control host (workstation Linux / macOS, or VPS)
- Targets: Linux VPS with SSH + Python 3
- SSH keys configured to reach targets
Step 1: Installation
sudo apt update
sudo apt install -y ansible
ansible --version
Or via pip:
sudo apt install -y python3-pip
pip install ansible
Step 2: Inventory
hosts.ini:
[web]
web1 ansible_host=192.168.1.10
web2 ansible_host=192.168.1.11
[db]
db1 ansible_host=192.168.1.20
[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_become=yes
Or YAML (inventory.yml):
all:
vars:
ansible_user: admin
ansible_become: yes
children:
web:
hosts:
web1:
ansible_host: 192.168.1.10
web2:
ansible_host: 192.168.1.11
db:
hosts:
db1:
ansible_host: 192.168.1.20
Step 3: Connectivity test (ad-hoc)
ansible -i hosts.ini all -m ping
web1 | SUCCESS => {"changed": false, "ping": "pong"}
web2 | SUCCESS => {"changed": false, "ping": "pong"}
db1 | SUCCESS => {"changed": false, "ping": "pong"}
Useful ad-hoc commands:
ansible -i hosts.ini web -m apt -a "name=nginx state=present" --become
ansible -i hosts.ini all -m shell -a "uptime"
ansible -i hosts.ini all -m setup
Step 4: First playbook
site.yml:
---
- name: Configure web servers
hosts: web
become: yes
tasks:
- name: Install packages
apt:
name:
- nginx
- htop
- curl
state: present
update_cache: yes
- name: Start nginx
systemd:
name: nginx
state: started
enabled: yes
- name: Deploy index page
copy:
content: "Hello from {{ inventory_hostname }}"
dest: /var/www/html/index.nginx-debian.html
owner: www-data
group: www-data
mode: '0644'
notify: Reload nginx
handlers:
- name: Reload nginx
systemd:
name: nginx
state: reloaded
Run:
ansible-playbook -i hosts.ini site.yml
Step 5: Variables and templates
group_vars/web.yml:
nginx_port: 80
domain: mysite.com
admin_email: [email protected]
Template templates/nginx.conf.j2:
server {
listen {{ nginx_port }};
server_name {{ domain }};
root /var/www/{{ domain }};
location / {
try_files $uri $uri/ =404;
}
}
In playbook:
- name: Deploy nginx config
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/{{ domain }}
notify: Reload nginx
Step 6: Conditionals and loops
- name: Install packages per OS
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- mariadb-server
- php-fpm
when: ansible_os_family == "Debian"
- name: Install Apache if not Nginx
apt:
name: apache2
state: present
when: web_server == "apache"
Step 7: Roles
roles/
webserver/
tasks/
main.yml
handlers/
main.yml
templates/
nginx.conf.j2
defaults/
main.yml
files/
logo.png
meta/
main.yml
roles/webserver/tasks/main.yml:
- name: Install nginx
apt:
name: nginx
state: present
- name: Deploy config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: Reload nginx
In site.yml:
- hosts: web
become: yes
roles:
- webserver
- common
Step 8: Ansible Vault (secrets)
ansible-vault create group_vars/all/vault.yml
mysql_root_password: "very-strong-secret"
api_key: "abc123xyz"
- name: Set mysql root password
mysql_user:
name: root
password: "{{ mysql_root_password }}"
Run with vault:
ansible-playbook -i hosts.ini site.yml --ask-vault-pass
Or with password file:
echo "your-pass" > .vault_pass
chmod 600 .vault_pass
ansible-playbook -i hosts.ini site.yml --vault-password-file .vault_pass
Step 9: Ansible Galaxy
ansible-galaxy install geerlingguy.nginx
ansible-galaxy install geerlingguy.mysql
- hosts: web
roles:
- geerlingguy.nginx
- geerlingguy.mysql
Browse: https://galaxy.ansible.com
Step 10: Dry-run and check mode
ansible-playbook -i hosts.ini site.yml --check
ansible-playbook -i hosts.ini site.yml --check --diff
Step 11: Tags
- name: Install nginx
apt:
name: nginx
state: present
tags: install
- name: Config nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-enabled/default
tags: config
ansible-playbook -i hosts.ini site.yml --tags config
ansible-playbook -i hosts.ini site.yml --skip-tags install
Step 12: Integration with Terraform
Typical workflow:
- Terraform provisions VPS and generates dynamic inventory
- Ansible configures provisioned VPS
terraform output -json | jq -r '...' > inventory.ini
Or use community.general.terraform_inventory.
Troubleshooting
"Failed to connect via SSH"
- Network connectivity (ping, traceroute)
- SSH key (
ssh user@targetmanually) - Correct
ansible_user - Python 3 installed on target
"Missing sudo password"
Add --ask-become-pass or set become_method: sudo in playbook.
Slow performance
ansible.cfg:
[ssh_connection]
pipelining = True
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible-facts
fact_caching_timeout = 86400
forks = 50
"ERROR! Unexpected Exception"
YAML syntax (indentation, spaces):
ansible-playbook --syntax-check site.yml
Unresolved variables
Check precedence (cli > playbook > host_vars > group_vars > role defaults).
ansible -i hosts.ini web1 -m debug -a "var=hostvars[inventory_hostname]"
Useful commands
ansible all -i hosts.ini -m ping
ansible web -i hosts.ini -m apt -a "name=htop state=present" --become
ansible-playbook -i hosts.ini site.yml
ansible-playbook -i hosts.ini site.yml --limit web1
ansible-playbook -i hosts.ini site.yml --check --diff
ansible-playbook -i hosts.ini site.yml --tags config
ansible-playbook -i hosts.ini site.yml --skip-tags slow
ansible-vault create file.yml
ansible-vault edit file.yml
ansible-vault encrypt existing.yml
ansible-vault decrypt encrypted.yml
ansible-galaxy install role_name
ansible-galaxy collection install community.general
ansible web1 -i hosts.ini -m setup
Conclusion
Ansible gives you:
- Agentless configuration management
- Idempotence (safe replay)
- Massive ecosystem (Galaxy)
- Easy Terraform integration
Going further:
- Use Ansible Tower / AWX for UI and CI/CD
- Combine with Terraform for provisioning
- For app deploys, consider ArgoCD (k8s) or Kamal (containers)
Resources
- Official docs: https://docs.ansible.com
- Ansible Galaxy: https://galaxy.ansible.com
- Ansible AWX: https://github.com/ansible/awx
- Best practices: https://docs.ansible.com/ansible/latest/tips_tricks/index.html

















