What is Ansible?
BeginnerAnsible is an open-source automation tool by Red Hat that automates configuration management, application deployment, and orchestration. It's agentless — it connects to targets via SSH and requires no software installed on managed nodes.
Terraform is best for provisioning infrastructure (creating servers, networks, etc.). Ansible is best for configuring and managing software on those servers. They complement each other perfectly.
Key Features
- Agentless — Uses SSH, no agent installation needed
- Idempotent — Running the same playbook twice produces the same result
- YAML-based — Easy to read and write
- Extensible — 3000+ modules for every use case
- Push-based — Changes are pushed from the control node
Installation
Beginner# Install on Ubuntu/Debian
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
# Or install via pip
pip install ansible
# Verify
ansible --version
# Test connectivity to a host
ansible all -m ping -i "webserver1.example.com,"
Inventory
BeginnerThe inventory file defines the hosts and groups Ansible manages.
# inventory/hosts.ini
[webservers]
web1.example.com ansible_host=10.0.1.10
web2.example.com ansible_host=10.0.1.11
[databases]
db1.example.com ansible_host=10.0.2.10
db2.example.com ansible_host=10.0.2.11
[loadbalancers]
lb1.example.com ansible_host=10.0.0.10
# Group of groups
[production:children]
webservers
databases
loadbalancers
# Group variables
[webservers:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/prod-key.pem
http_port=80
# YAML format inventory (inventory/hosts.yml)
all:
children:
webservers:
hosts:
web1.example.com:
ansible_host: 10.0.1.10
web2.example.com:
ansible_host: 10.0.1.11
vars:
ansible_user: ubuntu
http_port: 80
databases:
hosts:
db1.example.com:
ansible_host: 10.0.2.10
Ad-hoc Commands
Beginner# Ping all hosts
ansible all -m ping -i inventory/hosts.ini
# Run a command on webservers
ansible webservers -m shell -a "uptime" -i inventory/hosts.ini
# Install a package
ansible webservers -m apt -a "name=nginx state=present" --become -i inventory/hosts.ini
# Copy a file
ansible webservers -m copy -a "src=./index.html dest=/var/www/html/index.html" --become
# Gather facts about hosts
ansible webservers -m setup -i inventory/hosts.ini
# Restart a service
ansible webservers -m service -a "name=nginx state=restarted" --become
Playbooks
BeginnerPlaybooks are YAML files that define a series of tasks to execute on hosts.
# playbooks/setup-webserver.yml
---
- name: Setup Web Server
hosts: webservers
become: yes
vars:
app_name: myapp
app_port: 3000
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install required packages
apt:
name:
- nginx
- curl
- unzip
state: present
- name: Create application directory
file:
path: /opt/{{ app_name }}
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Copy application files
copy:
src: ../app/
dest: /opt/{{ app_name }}/
owner: www-data
group: www-data
- name: Configure Nginx
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
mode: '0644'
notify: Reload Nginx
- name: Enable Nginx site
file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link
notify: Reload Nginx
- name: Ensure Nginx is running
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Reload Nginx
service:
name: nginx
state: reloaded
# Run the playbook
ansible-playbook playbooks/setup-webserver.yml -i inventory/hosts.ini
# Dry run (check mode)
ansible-playbook playbooks/setup-webserver.yml --check
# Limit to specific hosts
ansible-playbook playbooks/setup-webserver.yml --limit web1.example.com
# With extra variables
ansible-playbook playbooks/setup-webserver.yml -e "app_port=8080"
Variables & Facts
IntermediateVariables can be defined at multiple levels with a clear precedence order.
# group_vars/webservers.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
app_environment: production
ssl_enabled: true
# host_vars/web1.example.com.yml
---
nginx_worker_processes: 4
custom_config: true
# Using variables and conditionals in tasks
- name: Install SSL certificate
copy:
src: "{{ ssl_cert_path }}"
dest: /etc/ssl/certs/app.crt
when: ssl_enabled | bool
- name: Configure based on OS
template:
src: "{{ item }}"
dest: /etc/myapp/config.conf
with_first_found:
- "templates/{{ ansible_distribution }}.conf.j2"
- "templates/default.conf.j2"
# Using Ansible facts
- name: Show system info
debug:
msg: |
Hostname: {{ ansible_hostname }}
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
IP: {{ ansible_default_ipv4.address }}
Memory: {{ ansible_memtotal_mb }} MB
CPUs: {{ ansible_processor_vcpus }}
Handlers & Templates
IntermediateJinja2 Templates
# templates/nginx.conf.j2
server {
listen {{ http_port | default(80) }};
server_name {{ ansible_fqdn }};
{% if ssl_enabled %}
listen 443 ssl;
ssl_certificate /etc/ssl/certs/{{ app_name }}.crt;
ssl_certificate_key /etc/ssl/private/{{ app_name }}.key;
{% endif %}
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
{% for header in extra_headers | default([]) %}
proxy_set_header {{ header.name }} {{ header.value }};
{% endfor %}
}
access_log /var/log/nginx/{{ app_name }}_access.log;
error_log /var/log/nginx/{{ app_name }}_error.log;
}
Roles
IntermediateRoles organize playbooks into reusable, shareable components.
# Create a role scaffold
ansible-galaxy role init roles/webserver
# Role structure:
# roles/webserver/
# ├── defaults/main.yml # Default variables (lowest precedence)
# ├── files/ # Static files to copy
# ├── handlers/main.yml # Handler definitions
# ├── meta/main.yml # Role metadata & dependencies
# ├── tasks/main.yml # Main task list
# ├── templates/ # Jinja2 templates
# ├── tests/ # Tests
# └── vars/main.yml # Variables (high precedence)
# roles/webserver/tasks/main.yml
---
- name: Install Nginx
apt:
name: nginx
state: present
tags: [install]
- name: Deploy configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: nginx -t -c %s
notify: Restart Nginx
tags: [config]
- name: Ensure service is running
service:
name: nginx
state: started
enabled: yes
tags: [service]
# Using roles in a playbook
---
- name: Configure Production Stack
hosts: all
become: yes
roles:
- role: common
- role: webserver
vars:
http_port: 80
ssl_enabled: true
- role: monitoring-agent
when: enable_monitoring | default(true)
Ansible Vault
IntermediateAnsible Vault encrypts sensitive data like passwords, API keys, and certificates.
# Create an encrypted file
ansible-vault create secrets.yml
# Encrypt an existing file
ansible-vault encrypt group_vars/production/secrets.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# Decrypt a file
ansible-vault decrypt secrets.yml
# Run playbook with vault password
ansible-playbook site.yml --ask-vault-pass
# Or use a password file
ansible-playbook site.yml --vault-password-file .vault_pass
# Encrypt a single string
ansible-vault encrypt_string 'my_secret_password' --name 'db_password'
# Using encrypted variables in playbooks
# group_vars/production/secrets.yml (encrypted)
---
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
616362383330...
api_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
336139653833...
Ansible Galaxy
Advanced# Install roles from Galaxy
ansible-galaxy install geerlingguy.docker
ansible-galaxy install geerlingguy.nginx
# Install from requirements file
# requirements.yml
---
roles:
- name: geerlingguy.docker
version: 7.1.0
- name: geerlingguy.nginx
version: 3.2.0
collections:
- name: amazon.aws
version: 7.0.0
- name: community.docker
version: 3.4.0
# Install all requirements
ansible-galaxy install -r requirements.yml
ansible-galaxy collection install -r requirements.yml
Dynamic Inventory
AdvancedDynamic inventory automatically discovers hosts from cloud providers or other sources.
# AWS EC2 dynamic inventory plugin
# inventory/aws_ec2.yml
---
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
- us-west-2
filters:
tag:Environment:
- production
- staging
instance-state-name: running
keyed_groups:
- key: tags.Environment
prefix: env
- key: tags.Role
prefix: role
- key: placement.availability_zone
prefix: az
compose:
ansible_host: public_ip_address
# Use dynamic inventory
ansible-inventory -i inventory/aws_ec2.yml --graph
# Run playbook with dynamic inventory
ansible-playbook -i inventory/aws_ec2.yml site.yml
Best Practices
Advanced- Use roles — Organize playbooks into reusable roles
- Use Ansible Vault — Encrypt all sensitive data
- Idempotency — Ensure playbooks can run safely multiple times
- Use tags — Tag tasks for selective execution
- Use handlers — Avoid unnecessary service restarts
- Test with Molecule — Use Molecule for role testing
- Lint your code — Use
ansible-lintfor best practices - Separate environments — Use different inventory files per environment
- Version pin roles — Pin Galaxy roles to specific versions
- Use
--checkmode — Always dry-run before applying changes
A well-organized Ansible project follows this structure:
ansible-project/
├── ansible.cfg
├── requirements.yml
├── site.yml # Master playbook
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ └── staging/
│ ├── hosts.yml
│ └── group_vars/
├── playbooks/
│ ├── webservers.yml
│ ├── databases.yml
│ └── monitoring.yml
├── roles/
│ ├── common/
│ ├── webserver/
│ ├── database/
│ └── monitoring/
└── files/