TL;DR
Ansible is an agentless automation tool that uses SSH and YAML playbooks to configure servers, deploy applications, and orchestrate infrastructure. Define your hosts in an inventory, write playbooks with tasks and handlers, organize reusable logic into roles, encrypt secrets with Vault, and use Jinja2 templates for dynamic configuration files. It works seamlessly with Docker, Kubernetes, and cloud providers. Format your YAML configs with the online YAML formatter.
1. What Is Ansible?
Ansible is an open-source IT automation engine developed by Red Hat. It automates configuration management, application deployment, cloud provisioning, and orchestration. Unlike Chef or Puppet, Ansible is agentless â it connects to managed nodes over SSH (or WinRM for Windows) and requires no software to be installed on target machines.
Ansible uses a push-based model: you run commands from a control node, and Ansible pushes changes to remote hosts. Playbooks are written in YAML, making them human-readable and easy to version control. The idempotent design means running a playbook multiple times produces the same result â it only changes what needs changing.
2. Installing Ansible
Ansible only needs to be installed on the control node (your workstation or a CI server). Managed nodes only need Python and SSH.
# Ubuntu / Debian
sudo apt update
sudo apt install -y ansible
# RHEL / CentOS / Fedora
sudo dnf install -y ansible-core
# macOS (Homebrew)
brew install ansible
# pip (any OS â recommended for latest version)
pip install ansible
# Verify installation
ansible --version
# ansible [core 2.16.x]3. Inventory Files â Defining Your Infrastructure
An inventory tells Ansible which hosts to manage. It can be a simple INI file, YAML file, or a dynamic script that queries cloud APIs. The default location is /etc/ansible/hosts.
INI Format
# inventory.ini
[webservers]
web1.example.com ansible_user=ubuntu
web2.example.com ansible_user=ubuntu
[dbservers]
db1.example.com ansible_user=root ansible_port=2222
[production:children]
webservers
dbservers
[production:vars]
ansible_ssh_private_key_file=~/.ssh/prod_keyYAML Format
# inventory.yml
all:
children:
webservers:
hosts:
web1.example.com:
ansible_user: ubuntu
web2.example.com:
ansible_user: ubuntu
dbservers:
hosts:
db1.example.com:
ansible_user: root
ansible_port: 22224. Ad-Hoc Commands â Quick One-Liners
Ad-hoc commands let you run a single task on remote hosts without writing a playbook. They are perfect for quick checks, one-off operations, and troubleshooting.
# Ping all hosts
ansible all -i inventory.ini -m ping
# Check disk space on webservers
ansible webservers -m shell -a "df -h"
# Install a package
ansible dbservers -m apt -a "name=postgresql state=present" --become
# Copy a file
ansible all -m copy -a "src=/tmp/config.conf dest=/etc/app/config.conf"
# Restart a service
ansible webservers -m service -a "name=nginx state=restarted" --become
# Gather facts
ansible web1.example.com -m setup5. Playbooks â Tasks, Handlers, and Variables
A playbook is a YAML file containing one or more plays. Each play targets a group of hosts and defines a list of tasks to execute in order. Playbooks are the core of Ansible automation.
Basic Playbook Structure
# site.yml
---
- name: Configure web servers
hosts: webservers
become: yes
vars:
http_port: 80
app_name: myapp
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Copy Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: Restart Nginx
- name: Ensure Nginx is running
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart Nginx
service:
name: nginx
state: restartedHandlers are special tasks that run only when notified by another task. They execute once at the end of the play, even if notified multiple times. This is ideal for restarting services only when configuration files actually change.
Variables in Playbooks
# Variable precedence (lowest to highest):
# 1. role defaults
# 2. inventory vars
# 3. playbook vars
# 4. role vars
# 5. task vars
# 6. extra vars (-e)
- name: Deploy with variables
hosts: webservers
vars:
app_version: "2.1.0"
deploy_dir: /opt/myapp
vars_files:
- vars/common.yml
- vars/production.yml
tasks:
- name: Create deploy directory
file:
path: "{{ deploy_dir }}"
state: directory
mode: "0755"
# Run with extra vars
# ansible-playbook site.yml -e "app_version=2.2.0"6. Essential Modules â apt, yum, copy, template, service, file, user
Ansible ships with thousands of modules. Here are the most commonly used ones for system administration.
Package Management (apt / yum)
# Install packages (Debian/Ubuntu)
- name: Install required packages
apt:
name:
- nginx
- python3-pip
- git
- curl
state: present
update_cache: yes
cache_valid_time: 3600
# Install packages (RHEL/CentOS)
- name: Install packages
yum:
name:
- httpd
- php
- mariadb-server
state: latestFile Operations (copy / template / file)
# Copy a static file
- name: Copy app config
copy:
src: files/app.conf
dest: /etc/myapp/app.conf
owner: root
group: root
mode: "0644"
backup: yes
# Deploy a Jinja2 template
- name: Deploy Nginx config
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: "nginx -t -c %s"
notify: Reload Nginx
# Create directories and set permissions
- name: Create app directories
file:
path: /opt/myapp/logs
state: directory
owner: appuser
group: appuser
mode: "0755"
recurse: yesService and User Management
# Manage services
- name: Start and enable Nginx
service:
name: nginx
state: started
enabled: yes
# Manage system users
- name: Create application user
user:
name: appuser
comment: "Application Service Account"
shell: /bin/bash
groups: www-data
append: yes
create_home: yes
# Add SSH key for user
- name: Add authorized key
authorized_key:
user: appuser
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"7. Roles and Ansible Galaxy
Roles are the primary mechanism for organizing and reusing Ansible content. A role groups tasks, handlers, templates, files, and variables into a standardized directory structure.
# Role directory structure
roles/
nginx/
tasks/
main.yml # Entry point for tasks
handlers/
main.yml # Handlers
templates/
nginx.conf.j2 # Jinja2 templates
files/
index.html # Static files
vars/
main.yml # Role variables (high precedence)
defaults/
main.yml # Default variables (easily overridden)
meta/
main.yml # Dependencies, metadataUsing Roles in Playbooks
# site.yml â using roles
---
- name: Configure web stack
hosts: webservers
become: yes
roles:
- common
- nginx
- role: app_deploy
vars:
app_version: "3.0.0"
- role: monitoring
when: enable_monitoring | default(true)Ansible Galaxy
# Initialize a new role skeleton
ansible-galaxy role init my_role
# Install a role from Galaxy
ansible-galaxy role install geerlingguy.docker
# Install roles from requirements file
# requirements.yml
---
roles:
- name: geerlingguy.docker
version: "7.1.0"
- name: geerlingguy.nginx
- name: geerlingguy.postgresql
collections:
- name: community.docker
version: "3.4.0"
# Install all from requirements
ansible-galaxy install -r requirements.yml8. Variables and Facts
Facts are system information automatically gathered from managed nodes. Ansible collects facts about the OS, network interfaces, disks, memory, and more using the setup module.
# Access facts in playbooks
- name: Show system information
debug:
msg: |
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
IP: {{ ansible_default_ipv4.address }}
CPUs: {{ ansible_processor_vcpus }}
RAM: {{ ansible_memtotal_mb }} MB
Hostname: {{ ansible_hostname }}
# Register task output as a variable
- name: Check if app is running
shell: systemctl is-active myapp
register: app_status
ignore_errors: yes
- name: Start app if not running
service:
name: myapp
state: started
when: app_status.rc != 0
# Custom facts (place in /etc/ansible/facts.d/*.fact)
# /etc/ansible/facts.d/app.fact
# [general]
# version=2.1.0
# env=production
# Access: {{ ansible_local.app.general.version }}9. Conditionals and Loops
Ansible supports when conditionals and various loop constructs to make playbooks dynamic and adaptable to different environments.
# Conditionals
- name: Install Apache on RedHat
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
- name: Install Apache on Debian
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
# Loops with loop (replaces with_items)
- name: Create multiple users
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
state: present
loop:
- { name: alice, groups: admin }
- { name: bob, groups: developers }
- { name: charlie, groups: developers }
# Loop with dict2items
- name: Set sysctl parameters
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
sysctl_set: yes
loop: "{{ sysctl_params | dict2items }}"
vars:
sysctl_params:
net.core.somaxconn: 65535
vm.swappiness: 1010. Jinja2 Templates
The template module renders Jinja2 templates with variables and logic, then deploys the result to remote hosts. Templates are essential for generating configuration files dynamically.
# templates/nginx.conf.j2
upstream app_backend {
{% for server in app_servers %}
server {{ server }}:{{ app_port | default(8080) }};
{% endfor %}
}
server {
listen {{ http_port | default(80) }};
server_name {{ domain_name }};
{% if ssl_enabled | default(false) %}
listen 443 ssl;
ssl_certificate /etc/ssl/{{ domain_name }}.crt;
ssl_certificate_key /etc/ssl/{{ domain_name }}.key;
{% endif %}
location / {
proxy_pass http://app_backend;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
# Generated by Ansible on {{ ansible_date_time.iso8601 }}
# Managed by: {{ ansible_user_id }}
}Jinja2 provides powerful filters: {{ list | join(",") }}, {{ var | default("fallback") }},{{ password | hash("sha512") }}, and {{ path | basename }}.
11. Ansible Vault â Managing Secrets
Ansible Vault encrypts sensitive data (passwords, API keys, certificates) so they can be safely stored in version control. Files are encrypted with AES-256.
# Create / encrypt / edit / view encrypted files
ansible-vault create secrets.yml
ansible-vault encrypt vars/production.yml
ansible-vault edit secrets.yml
ansible-vault view secrets.yml
# Encrypt a single string (inline vault)
ansible-vault encrypt_string 'SuperSecret123' --name 'db_password'
# Run playbook with vault password
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass
# Multiple vault IDs for different environments
ansible-playbook site.yml \
--vault-id dev@~/.vault_dev \
--vault-id prod@~/.vault_prod12. Error Handling
Ansible provides block/rescue/always for structured error handling, similar to try/catch/finally in programming languages.
- name: Deploy with error handling
hosts: webservers
tasks:
- name: Deployment block
block:
- name: Pull latest code
git:
repo: https://github.com/org/app.git
dest: /opt/app
version: main
- name: Run database migrations
command: /opt/app/manage.py migrate
- name: Restart application
service:
name: myapp
state: restarted
rescue:
- name: Rollback on failure
command: /opt/app/rollback.sh
- name: Send failure notification
mail:
to: ops@example.com
subject: "Deployment FAILED on {{ inventory_hostname }}"
body: "Deployment failed. Rollback executed."
always:
- name: Cleanup temp files
file:
path: /tmp/deploy_artifacts
state: absent
# Retry with custom failure condition
- name: Check URL (with retries)
uri:
url: http://localhost:8080/health
register: health_check
retries: 5
delay: 10
until: health_check.status == 20013. Tags â Selective Task Execution
Tags let you run specific subsets of tasks in a playbook. This is invaluable for large playbooks where you only need to update one part of the configuration.
- name: Full server setup
hosts: all
tasks:
- name: Install base packages
apt:
name: [vim, htop, curl, git]
state: present
tags: [packages, base]
- name: Configure firewall
ufw:
rule: allow
port: "{{ item }}"
loop: ["22", "80", "443"]
tags: [firewall, security]
- name: Deploy application
copy:
src: app/
dest: /opt/myapp/
tags: [deploy, app]
# Run only specific tags
# ansible-playbook site.yml --tags "deploy"
# ansible-playbook site.yml --tags "security,packages"
# Skip specific tags
# ansible-playbook site.yml --skip-tags "firewall"
# List all tags in a playbook
# ansible-playbook site.yml --list-tags14. Dynamic Inventory
Static inventory files work for small environments, but cloud infrastructure changes constantly. Dynamic inventory scripts or plugins query cloud APIs (AWS, GCP, Azure) to discover hosts automatically.
# AWS EC2 dynamic inventory plugin
# aws_ec2.yml
---
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
- us-west-2
filters:
tag:Environment:
- production
instance-state-name:
- running
keyed_groups:
- key: tags.Role
prefix: role
- key: placement.region
prefix: aws_region
compose:
ansible_host: public_ip_address
# Use dynamic inventory
# ansible-inventory -i aws_ec2.yml --list
# ansible-playbook -i aws_ec2.yml site.ymlFor GCP, use google.cloud.gcp_compute; for Azure, use azure.azcollection.azure_rm. All follow the same pattern: filters, authentication, and host grouping.
15. Ansible with Docker and Kubernetes
Ansible integrates with container platforms through dedicated collections: community.docker for Docker and kubernetes.core for Kubernetes.
Docker Management
# Install collection first:
# ansible-galaxy collection install community.docker
- name: Manage Docker containers
hosts: docker_hosts
tasks:
- name: Pull application image
community.docker.docker_image:
name: myapp
tag: "2.0"
source: pull
- name: Run application container
community.docker.docker_container:
name: myapp
image: "myapp:2.0"
state: started
restart_policy: unless-stopped
ports:
- "8080:8080"
env:
DB_HOST: "db.internal"
NODE_ENV: "production"
volumes:
- /data/app:/app/dataKubernetes Deployments
# ansible-galaxy collection install kubernetes.core
- name: Deploy to Kubernetes
hosts: localhost
tasks:
- name: Create namespace
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: myapp-prod
- name: Deploy application from manifest
kubernetes.core.k8s:
state: present
src: k8s/deployment.yml
namespace: myapp-prod16. AWX and Ansible Automation Platform (Tower)
AWX is the open-source upstream project for the Red Hat Ansible Automation Platform (formerly Ansible Tower). It adds a web UI, REST API, role-based access control, job scheduling, and centralized credential management on top of command-line Ansible.
# Deploy AWX on Kubernetes via AWX Operator
kubectl apply -f https://raw.githubusercontent.com/ansible/\
awx-operator/main/deploy/awx-operator.yml
# awx-instance.yml
---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
name: awx
namespace: awx
spec:
service_type: NodePort
kubectl apply -f awx-instance.yml
kubectl get pods -n awx -wKey AWX features include Job Templates, Workflows (chaining templates), RBAC, encrypted credential storage, cron-like scheduling, and a REST API for CI/CD integration.
17. Ansible Best Practices
Following these conventions keeps your Ansible projects maintainable as they grow.
# Recommended project layout
ansible-project/
inventories/
production/
hosts.yml
group_vars/all.yml
host_vars/web1.example.com.yml
staging/
hosts.yml
roles/
common/
nginx/
app/
playbooks/
site.yml
webservers.yml
ansible.cfg
requirements.ymlKey best practices:
- Always name your tasks clearly â
name: Install Nginxbeats an unnamed task. - Use roles for reusability; keep playbooks as thin orchestration layers.
- Pin role and collection versions in
requirements.yml. - Store secrets with Ansible Vault, never in plain text.
- Use
ansible-lintto enforce coding standards. - Test with Molecule before deploying to production.
- Use
--check(dry run) and--diffto preview changes. - Keep inventory per environment in separate directories.
- Use
group_varsandhost_varsinstead of inline variables. - Avoid using
shell/commandwhen a dedicated module exists.
# ansible.cfg â project-level configuration
[defaults]
inventory = inventories/production/hosts.yml
roles_path = roles
retry_files_enabled = False
stdout_callback = yaml
forks = 20
timeout = 30
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s18. Ansible vs Terraform vs Chef vs Puppet
| Feature | Ansible | Terraform | Chef | Puppet |
|---|---|---|---|---|
| Primary Use | Config management | Infrastructure provisioning | Config management | Config management |
| Language | YAML | HCL | Ruby DSL | Puppet DSL |
| Agent | Agentless (SSH) | Agentless (API) | Agent required | Agent required |
| Model | Push | Push | Pull | Pull |
| State | Stateless | State file | Chef Server | PuppetDB |
| Learning Curve | Low | Medium | High | Medium-High |
| Best For | Server config, app deploy | Cloud infra provisioning | Complex enterprise config | Large-scale compliance |
Many teams combine Terraform + Ansible: Terraform provisions infrastructure (VMs, networks, load balancers), then Ansible configures those machines (install packages, deploy apps, manage services). Use the YAML formatter to keep both Terraform YAML and Ansible playbooks clean.
Key Takeaways
- Agentless by design: Ansible uses SSH and requires no agent installation on managed nodes, simplifying setup dramatically.
- YAML playbooks: human-readable, version-controllable, and idempotent; running them twice produces the same result.
- Roles for reusability: break complex automation into roles and share them via Ansible Galaxy.
- Vault for secrets: encrypt passwords and API keys with AES-256; use vault IDs for multi-environment setups.
- Jinja2 templates: generate dynamic configuration files with variables, loops, and conditionals.
- Dynamic inventory: query AWS, GCP, or Azure APIs to discover hosts automatically instead of hardcoding IPs.
- Docker and Kubernetes modules: manage containers and cluster resources directly from playbooks.
- Ansible + Terraform: use Terraform to provision infrastructure, then Ansible to configure it; they complement each other.
Frequently Asked Questions
What is Ansible and how does it work?
Ansible is an agentless automation tool that connects over SSH and executes tasks from YAML playbooks. It uses a push model and requires no software on managed nodes.
What is the difference between Ansible and Terraform?
Terraform provisions infrastructure (VMs, networks) with a state file. Ansible configures existing machines (packages, services). Many teams use both together.
What is the difference between Ansible and Chef/Puppet?
Ansible is agentless (SSH) with YAML playbooks. Chef uses Ruby DSL and Puppet its own language, both requiring agents. Ansible pushes; Chef and Puppet pull.
How do I manage secrets in Ansible securely?
Use Ansible Vault for AES-256 encryption. Create encrypted files with ansible-vault create, encrypt strings inline, and run with --ask-vault-pass or a password file.
What are Ansible roles and why should I use them?
Roles package tasks, handlers, templates, and variables into a reusable directory structure. Share them via Ansible Galaxy and compose across projects.
How does Ansible work with Docker and Kubernetes?
Use community.docker for containers and images, kubernetes.core for pods and deployments. Ansible orchestrates the full lifecycle from build to rollout.
What is Ansible AWX/Tower and when do I need it?
AWX adds a web UI, REST API, RBAC, and job scheduling on top of CLI Ansible. Use it when teams need shared access, audit trails, or scheduled automation runs.
How do I handle errors and failures in Ansible playbooks?
Use block/rescue/always for try/catch/finally handling. Combine with ignore_errors, failed_when, and retries/until for robust automation.