Ansible configures servers without installing agents. SSH in, run tasks, done. Here’s how to write playbooks that actually work.
Why Ansible?# Agentless : Uses SSH, nothing to install on targetsIdempotent : Run it twice, same resultReadable : YAML syntax, easy to understandExtensible : Huge module libraryInventory# Define your servers in /etc/ansible/hosts or a custom file:
1
2
3
4
5
6
7
8
9
10
# inventory.ini
[webservers]
web1.example.com
web2.example.com
[databases]
db1.example.com ansible_user = postgres
[all:vars]
ansible_python_interpreter = /usr/bin/python3
Your First Playbook# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# site.yml
---
- name : Configure web servers
hosts : webservers
become : yes
tasks :
- name : Install nginx
apt :
name : nginx
state : present
update_cache : yes
- name : Start nginx
service :
name : nginx
state : started
enabled : yes
Run it:
1
ansible-playbook -i inventory.ini site.yml
Common Modules# Package Management# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Debian/Ubuntu
- name : Install packages
apt :
name :
- nginx
- curl
- vim
state : present
update_cache : yes
# RHEL/CentOS
- name : Install packages
yum :
name : nginx
state : present
Files and Templates# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- name : Copy config file
copy :
src : nginx.conf
dest : /etc/nginx/nginx.conf
owner : root
mode : '0644'
notify : Restart nginx
- name : Create directory
file :
path : /var/www/app
state : directory
owner : www-data
mode : '0755'
- name : Template config
template :
src : app.conf.j2
dest : /etc/app/config.conf
notify : Restart app
Services# 1
2
3
4
5
6
7
8
9
10
- name : Ensure nginx is running
service :
name : nginx
state : started
enabled : yes
- name : Restart service
service :
name : nginx
state : restarted
Users# 1
2
3
4
5
6
7
8
9
10
11
- name : Create app user
user :
name : appuser
shell : /bin/bash
groups : www-data
append : yes
- name : Add SSH key
authorized_key :
user : appuser
key : "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
Commands# 1
2
3
4
5
6
7
8
9
10
11
12
- name : Run a command
command : /opt/app/setup.sh
args :
creates : /opt/app/.installed
- name : Run shell command
shell : cat /etc/passwd | grep root
register : result
- name : Show output
debug :
var : result.stdout
Variables# 1
2
3
4
5
6
7
8
9
10
11
# In playbook
- hosts : webservers
vars :
app_port : 8080
app_name : myapp
tasks :
- name : Configure app
template :
src : app.conf.j2
dest : "/etc/{{ app_name }}/config.conf"
1
2
3
4
# In separate file (group_vars/webservers.yml)
app_port : 8080
app_name : myapp
db_host : db1.example.com
1
2
3
4
5
6
7
8
9
# In template (app.conf.j2)
server {
listen {{ app_port }};
server_name {{ ansible_hostname }};
location / {
proxy_pass http://localhost:3000;
}
}
Handlers# Handlers run once at the end, only if notified:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- hosts : webservers
tasks :
- name : Update nginx config
template :
src : nginx.conf.j2
dest : /etc/nginx/nginx.conf
notify : Restart nginx
- name : Update SSL cert
copy :
src : ssl.crt
dest : /etc/nginx/ssl.crt
notify : Restart nginx
handlers :
- name : Restart nginx
service :
name : nginx
state : restarted
Conditionals# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
- name : Install on Debian
apt :
name : nginx
when : ansible_os_family == "Debian"
- name : Install on RedHat
yum :
name : nginx
when : ansible_os_family == "RedHat"
- name : Only if variable set
debug :
msg : "App is {{ app_name }}"
when : app_name is defined
Loops# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- name : Create users
user :
name : "{{ item }}"
state : present
loop :
- alice
- bob
- charlie
- name : Install packages
apt :
name : "{{ item }}"
state : present
loop :
- nginx
- curl
- git
- name : Create files from dict
copy :
dest : "/etc/app/{{ item.key }}.conf"
content : "{{ item.value }}"
loop : "{{ config_files | dict2items }}"
Roles# Organize playbooks into reusable roles:
r o l n e g s i t h t d v / n a a e e a x s n m f r / k d p a s s l l u / / e a l m m r t t a a s e s i i / s n n m m . . a n a y y i g i m m n i n l l . n . y x y m . m l c l o n f . j 2 1
2
3
4
5
6
7
8
9
10
11
12
# roles/nginx/tasks/main.yml
---
- name : Install nginx
apt :
name : nginx
state : present
- name : Configure nginx
template :
src : nginx.conf.j2
dest : /etc/nginx/nginx.conf
notify : Restart nginx
1
2
3
4
5
6
# site.yml
---
- hosts : webservers
roles :
- nginx
- app
Ansible Vault# Encrypt sensitive data:
1
2
3
4
5
6
7
8
# Create encrypted file
ansible-vault create secrets.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
1
2
3
# secrets.yml (encrypted)
db_password : supersecret123
api_key : sk-xxxxx
1
2
3
4
5
6
7
8
9
10
# playbook using secrets
- hosts : webservers
vars_files :
- secrets.yml
tasks :
- name : Configure database
template :
src : db.conf.j2
dest : /etc/app/db.conf
Best Practices# 1
2
3
4
5
6
7
8
9
10
11
12
13
# Use meaningful names
- name : Install nginx web server # Good
- name : Install # Bad
# Use become for privilege escalation
- hosts : all
become : yes # Run as root
# Check before making changes
ansible-playbook site.yml --check --diff
# Limit to specific hosts
ansible-playbook site.yml --limit web1.example.com
Quick Reference# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Run playbook
ansible-playbook site.yml
# With inventory file
ansible-playbook -i inventory.ini site.yml
# Check mode (dry run)
ansible-playbook site.yml --check
# Limit hosts
ansible-playbook site.yml --limit webservers
# Extra variables
ansible-playbook site.yml -e "app_port=9000"
# With vault password
ansible-playbook site.yml --ask-vault-pass
# Verbose output
ansible-playbook site.yml -vvv
# List tasks
ansible-playbook site.yml --list-tasks
Ansible turns server configuration into version-controlled code. Write once, run anywhere, get consistent results every time.
π¬ Get the Newsletter Weekly insights on DevOps, automation, and CLI mastery. No spam, unsubscribe anytime.