A practical reference from first contact to writing your own modules
Table of Contents
- What Ansible Is and How It Thinks
- Ansible Architecture
- Installing and Setting Up Ansible
- Inventories: Static, Dynamic, and Cloud
- YAML and Ansible Syntax Basics
- Ad-hoc Commands vs Playbooks
- Playbooks in Depth
- Variables and Variable Precedence
- Jinja2 Templating
- Conditionals, Loops, Blocks, Rescue, Always
- Roles: Structure and Best Practices
- Collections: Organizing and Sharing Content
- Core Built-in Modules You’ll Use Constantly
- Cloud Automation (AWS, Azure, GCP)
- Network Automation
- Managing Secrets with Ansible Vault
- Error Handling and Debugging
- Idempotency: Doing Things Once, Correctly
- Performance and Scaling Ansible
- Using Ansible in CI/CD Pipelines
- Custom Filters and Plugins
- Writing Your Own Ansible Module (Python)
- Ansible for Kubernetes
- Security Hardening and Compliance
- Common Architecture Patterns and Real Use Cases
- Common Gotchas and Misconceptions
- End-to-End Example: Provision and Deploy a Simple App
- Testing: Molecule and Other Strategies
- Terraform, Other Tools, and When Not to Use Ansible
- Final Cheat Sheet
What Ansible Is and How It Thinks
Ansible is:
- Configuration management (install packages, configure services)
- Orchestration (deploy apps across multiple hosts in order)
- Provisioning and automation (create cloud resources, bootstrap servers)
Key properties:
- Agentless – no agent on managed nodes; it uses SSH (Linux) or WinRM (Windows).
- Declarative-ish – you describe the desired state; modules try to enforce it idempotently.
- YAML-based – human-readable playbooks.
- Extensible – you can create roles, collections, plugins, and modules.
A mental model:
- You have a control node (where Ansible runs).
- You have managed nodes (servers, switches, containers, etc.).
- You describe what you want in playbooks.
- Ansible reads inventory to know where to apply it.
- It uses modules to perform actions (e.g.,
user,yum,apt,file). - It applies tasks in order, evaluates conditions, and tries to leave the system in a consistent state.
Ansible Architecture
At a high level:
flowchart LR
A[Control Node] -->|SSH / WinRM / API| B[Managed Node 1]
A --> C[Managed Node 2]
A --> D[Network Devices]
A --> E[Cloud APIs]
subgraph Control Node
P[Playbooks]
I[Inventory]
M[Modules]
PL[Plugins]
R[Roles & Collections]
end
Key components:
- Control Node
Where you install Ansible and run commands (ansible,ansible-playbook). Can be your laptop, a jump host, CI runner, etc. - Managed Nodes
Targets (Linux, Windows, network gear, cloud APIs). They don’t need Ansible installed, just Python (in many cases) and connectivity. - Inventory
List of hosts and groups. Can be static (INI/YAML) or dynamic (scripts/plugins from AWS, Azure, etc.). - Modules
Discrete units of work. Think “actions”: manage files, packages, services, cloud resources, etc. - Plugins
Extend behavior: connection plugins, callback plugins (logging/output), filter plugins (for Jinja2), lookup plugins, etc. - Facts
Data collected from managed nodes (ansible_facts): OS, IPs, CPUs, memory, etc.
Installing and Setting Up Ansible
On Linux
Most common:
# On modern distributions, prefer pipx or venv
python3 -m venv ~/ansible-venv
source ~/ansible-venv/bin/activate
pip install ansible
Or from your package manager (versions may lag):
# Debian/Ubuntu
sudo apt update
sudo apt install ansible
# RHEL/CentOS (may require EPEL or AppStream)
sudo dnf install ansible
On macOS
Using Homebrew:
brew install ansible
Or via pip in a virtualenv (same as Linux).
On Windows
Typical options:
- Use WSL (Windows Subsystem for Linux) and install Ansible inside Ubuntu.
- Or use a Linux VM as your control node.
Direct native support on Windows as a control node is not the common path; most people use Linux/WSL.
Using Docker
Run Ansible via container:
docker run --rm -it \
-v $(pwd):/workdir \
-w /workdir \
quay.io/ansible/ansible-runner:latest bash
Then use ansible-playbook inside the container.
Inventories: Static, Dynamic, and Cloud
The inventory tells Ansible what hosts exist and how to group them.
Static Inventory (INI)
inventory.ini:
[web]
web1.example.com
web2.example.com
[db]
db1.example.com
[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_rsa
Run:
ansible all -i inventory.ini -m ping
Static Inventory (YAML)
inventory.yml:
all:
vars:
ansible_user: ubuntu
children:
web:
hosts:
web1.example.com:
web2.example.com:
db:
hosts:
db1.example.com:
Host and Group Variables
Directory layout:
inventory/
hosts.yml
group_vars/
all.yml
web.yml
host_vars/
web1.example.com.yml
group_vars/web.yml:
nginx_port: 80
host_vars/web1.example.com.yml:
nginx_port: 8080 # overrides group var for this host
Dynamic Inventory
Instead of listing hosts yourself, you let a plugin/script talk to your cloud provider.
Examples:
- AWS:
aws_ec2plugin - Azure:
azure_rmplugin - GCP:
gcp_computeplugin
Dynamic inventory configuration (e.g., aws_ec2.yml):
plugin: aws_ec2
regions:
- us-east-1
keyed_groups:
- key: tags.Role
prefix: role_
Then:
ansible-inventory -i aws_ec2.yml --graph
ansible role_web -m ping -i aws_ec2.yml
YAML and Ansible Syntax Basics
YAML is whitespace-sensitive and absolutely hates tabs. Use spaces.
A task:
- name: Install nginx
apt:
name: nginx
state: present
become: true
Key YAML patterns:
- Lists:
packages: - nginx - git - curl - Dictionaries:
user: name: deploy shell: /bin/bash
Common mistakes:
- Using tabs instead of spaces → parse errors.
- Misaligned indentation → “mapping values are not allowed here”.
- Forgetting
-for list items.
Ad-hoc Commands vs Playbooks
Ad-hoc commands are great for quick one-off tasks.
# Ping all hosts
ansible all -i inventory.ini -m ping
# Install package
ansible web -m apt -a "name=nginx state=present" --become
# Run a shell command
ansible db -m shell -a "df -h"
Playbooks are YAML files describing reproducible workflows.
- Ad-hoc: “Do X now.”
- Playbook: “Define how X should always be.”
Playbooks in Depth
A playbook is a list of plays. Each play targets some hosts and runs tasks.
Example site.yml:
- name: Configure web servers
hosts: web
become: true
vars:
app_user: deploy
tasks:
- name: Ensure app user exists
user:
name: "{{ app_user }}"
shell: /bin/bash
- name: Install nginx
apt:
name: nginx
state: present
notify: Restart nginx
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
Concepts:
hosts: inventory group or pattern.become: use privilege escalation (usuallysudo).tasks: ordered list of modules with arguments.handlers: triggered bynotify; run at end of play or whenmeta: flush_handlersis used.vars: play-level variables.
Includes and Imports
import_playbook: static import at parse time.include_tasks/import_tasks: bring in task sets.
Example:
- import_playbook: db.yml
- hosts: web
tasks:
- import_tasks: tasks/common.yml
- include_tasks: tasks/dynamic.yml
when: ansible_os_family == "Debian"
Tags
Tag tasks to run subsets:
- name: Install nginx
apt:
name: nginx
state: present
tags:
- packages
- nginx
Run only those tasks:
ansible-playbook site.yml --tags nginx
Variables and Variable Precedence
Variables are everywhere: inventories, playbooks, roles, extra vars, etc.
Types of var sources:
- Inventory (
group_vars,host_vars) - Play vars (
vars:in play) - Role defaults (
roles/myrole/defaults/main.yml) - Role vars (
roles/myrole/vars/main.yml) - Extra vars (
-e) - Facts (
ansible_facts) - Registered vars (
register:)
Rough idea of precedence (highest wins):
- Extra vars (
-e) – always win. - Play vars.
- Host vars.
- Group vars.
- Role vars.
- Role defaults – lowest priority.
Practical habits:
- Put sane defaults in
roles/rolename/defaults/main.yml. - Use
group_vars/allfor cross-environment shared settings. - Use
group_vars/prodvsgroup_vars/stageto separate environments. - Avoid
-eexcept for overrides or secrets (or CI).
register
Capture module output:
- name: Get nginx version
command: nginx -v
register: nginx_version
failed_when: false
- debug:
var: nginx_version.stderr
Jinja2 Templating
Ansible uses Jinja2 to render:
- Variables:
{{ var_name }} - Conditionals and loops in templates.
- Filters:
{{ var | upper }}
Example templates/nginx.conf.j2:
user {{ nginx_user }};
worker_processes auto;
http {
server {
listen {{ nginx_port }};
server_name {{ inventory_hostname }};
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
}
}
}
Playbook:
- name: Deploy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginx
Useful filters:
default:{{ myvar | default('fallback') }}to_nice_json,to_nice_yamlupper,lower,replace,regex_replacejoin:{{ mylist | join(',') }}unique,sort,flatten
Control structures in templates:
{% if env == 'prod' %}
log_level warning;
{% else %}
log_level debug;
{% endif %}
upstream app {
{% for host in groups['web'] %}
server {{ hostvars[host].ansible_host }}:{{ app_port }};
{% endfor %}
}
Conditionals, Loops, Blocks, Rescue, Always
when
- name: Install nginx on Debian family
apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
Use variables, facts, registered vars:
- name: Do something only if file changed
debug:
msg: "File changed!"
when: copy_result.changed
Loops
Modern way: loop:
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- curl
Loop over dicts:
- name: Create users
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
loop:
- { name: "alice", groups: "sudo" }
- { name: "bob", groups: "docker" }
Blocks, Rescue, Always
Blocks group tasks logically and handle failures.
- block:
- name: Try risky operation
command: /bin/false
- name: This won’t run if previous failed
debug:
msg: "Still in block"
rescue:
- name: Handle failure
debug:
msg: "The block failed; handling it."
always:
- name: Always run cleanup
debug:
msg: "Cleanup step"
Useful for:
- Rolling back changes.
- Ensuring cleanup happens even on failure.
Roles: Structure and Best Practices
Roles are how you package reusable logic.
Standard Role Layout
roles/
nginx/
defaults/
main.yml
vars/
main.yml
tasks/
main.yml
handlers/
main.yml
templates/
nginx.conf.j2
files/
some-static-file
meta/
main.yml
tests/
inventory
test.yml
defaults/main.yml: lowest-precedence defaults.vars/main.yml: higher-precedence vars (use sparingly).tasks/main.yml: entry point for tasks.handlers/main.yml: notifications.templates/: Jinja2 templates.files/: raw files to copy.meta/main.yml: role dependencies.
Example use in playbook:
- hosts: web
become: true
roles:
- role: nginx
vars:
nginx_port: 8080
Role Dependencies
roles/app/meta/main.yml:
dependencies:
- role: nginx
- role: postgresql
Ansible will apply nginx and postgresql roles before app.
Best Practices for Roles
- Keep roles single responsibility (e.g.,
nginx,app_java,prometheus_node_exporter). - Avoid hardcoding environment-specific stuff in roles; push that to
group_vars. - Use
defaults/instead ofvars/unless you really want to override users. - Document variables in
README.mdinside the role.
Collections: Organizing and Sharing Content
Collections bundle:
- Roles
- Modules
- Plugins
- Playbooks
Layout:
my_namespace-my_collection/
plugins/
modules/
filters/
callbacks/
roles/
playbooks/
docs/
galaxy.yml
Use content from a collection with fully qualified collection names (FQCN):
- name: Use community.general module
community.general.htpasswd:
path: /etc/nginx/.htpasswd
name: admin
password: secret
Installing collections:
ansible-galaxy collection install community.general
Custom collection publishing is done via ansible-galaxy collection build and ansible-galaxy collection publish (often to private Galaxy servers or Automation Hub).
Core Built-in Modules You’ll Use Constantly
Instead of listing every module, here’s the useful families.
File Management
file: create directories, change ownership/mode, symlinks.copy: copy files to remote.template: render Jinja2 templates.lineinfile,blockinfile: modify specific lines or blocks in a file.
Example:
- name: Ensure directory exists
file:
path: /opt/myapp
state: directory
owner: deploy
group: deploy
mode: "0755"
Package Management
apt(Debian/Ubuntu)yum,dnf(RHEL/CentOS/Fedora)package(generic wrapper)
- name: Install nginx using generic module
package:
name: nginx
state: present
Service Management
service,systemd
- name: Enable and start nginx
systemd:
name: nginx
enabled: true
state: started
User / Group Management
user,group,authorized_key
- name: Create deploy user
user:
name: deploy
shell: /bin/bash
- name: Add SSH key
authorized_key:
user: deploy
key: "{{ lookup('file', 'files/deploy_id_rsa.pub') }}"
Command Execution
shell: runs commands through shell (supports pipes, redirects).command: runs commands directly (no shell).raw: sends raw commands (useful when Python is not installed yet).
Use these sparingly; prefer idempotent modules.
Cloud Automation (AWS, Azure, GCP)
Ansible has rich cloud collections:
- AWS:
amazon.aws,community.aws - Azure:
azure.azcollection - GCP:
google.cloud
Example: create an EC2 instance (simplified):
- hosts: localhost
connection: local
gather_facts: false
collections:
- amazon.aws
tasks:
- name: Launch EC2 instance
ec2_instance:
name: "web-server"
key_name: my-key
instance_type: t3.micro
image_id: ami-1234567890abcdef0
wait: true
region: us-east-1
register: ec2
Common patterns:
- Use dynamic inventory from the same provider.
- Use Ansible to bootstrap after initial provisioning (install agents, configure app).
Network Automation
Network device support is largely via collections:
cisco.ios,arista.eos,junipernetworks.junos, etc.
Core concepts:
- Use connection types like
network_cliorhttpapi. - Use NAPALM-based modules or vendor-specific modules.
- Always test safely (
check_mode, proper backups).
Example:
- hosts: switches
connection: network_cli
gather_facts: false
tasks:
- name: Configure interface
ios_config:
lines:
- description Uplink
- switchport mode trunk
parents: interface GigabitEthernet0/1
Managing Secrets with Ansible Vault
Vault encrypts sensitive data (passwords, API keys).
Create an encrypted file:
ansible-vault create group_vars/prod/vault.yml
Inside:
db_password: supersecret
Use in playbooks:
- name: Use secret
debug:
msg: "DB password is {{ db_password }}"
Run with vault password:
ansible-playbook site.yml --ask-vault-pass
# or
ansible-playbook site.yml --vault-password-file .vault_pass.txt
Best practices:
- Keep secrets in separate vars files, e.g.
vault.yml. - Never store vault pass file in the same repo.
- Consider external secret managers (HashiCorp Vault, AWS SSM) with lookup plugins for large environments.
Error Handling and Debugging
Useful CLI flags:
-v,-vv,-vvv: increase verbosity.--step: confirm each task interactively.--start-at-task: resume from a specific task.
Debug in tasks:
- debug:
var: some_variable
- debug:
msg: "Value is {{ some_variable | default('not set') }}"
Control failures:
- name: This may fail but is not fatal
command: /usr/bin/false
register: cmd
ignore_errors: true
- name: Fail if condition
fail:
msg: "Something went wrong"
when: cmd.rc != 0
Or:
failed_when: "'CRITICAL' in cmd.stderr"
changed_when: false
Idempotency: Doing Things Once, Correctly
Idempotency means: running the playbook again doesn’t break things; it leaves the system in the same state.
Good example:
- name: Install nginx (idempotent)
apt:
name: nginx
state: present
Bad example:
- name: Append line (not idempotent)
shell: "echo 'include /etc/nginx/conf.d/*.conf;' >> /etc/nginx/nginx.conf"
Better:
- name: Ensure line exists
lineinfile:
path: /etc/nginx/nginx.conf
line: "include /etc/nginx/conf.d/*.conf;"
Use module options like creates, removes, chdir to ensure idempotency when using command/shell:
- name: Run migration only once
command: /opt/app/bin/migrate
args:
creates: /opt/app/.migration_done
Performance and Scaling Ansible
When you grow to hundreds/thousands of hosts, performance matters.
Tips:
- Increase forks: parallelism.
# ansible.cfg [defaults] forks = 50 - Enable pipelining and SSH multiplexing:
[ssh_connection] pipelining = True control_path = ~/.ssh/ansible-%%h-%%p-%%rEnsurerequirettyis disabled insudoersif you usepipelining. - Use fact caching (Redis, JSON files) to avoid recollecting facts every run.
- Use
serialfor rolling updates:- hosts: web serial: 10 tasks: - name: Update app include_role: app - Use
strategy: freeto let hosts run tasks independently:- hosts: all strategy: free tasks: - name: Long task per host command: /usr/local/bin/do_something_long
Using Ansible in CI/CD Pipelines
Typical stages:
- Lint:
ansible-lint - Unit/integration tests:
molecule - Dry-run:
ansible-playbook --check - Apply:
ansible-playbookto staging, then prod.
Example GitHub Actions job (idea, not full YAML):
jobs:
ansible:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Ansible
run: |
python -m pip install --upgrade pip
pip install ansible ansible-lint molecule
- name: Lint playbooks
run: ansible-lint
- name: Run playbook in check mode
run: ansible-playbook -i inventory site.yml --check
For production, you might:
- Run playbooks from a bastion host via CI.
- Use vault or external secret managers for credentials.
Custom Filters and Plugins
You can extend Jinja2 with custom filters.
Filter Plugin Example
filter_plugins/my_filters.py:
def reverse_string(value):
return value[::-1]
class FilterModule(object):
def filters(self):
return {
'reverse_string': reverse_string
}
Use in playbook/template:
{{ "ansible" | reverse_string }} {# outputs 'elbisna' #}
Other plugin types:
- Lookup plugins: fetch data from external systems.
- Callback plugins: custom logging/formatting of output.
- Action plugins: change how modules are executed.
Writing Your Own Ansible Module (Python)
This is the “advanced mode” you asked for.
Modules are usually:
- Single Python file in
library/or inside a collection. - Use
AnsibleModulefromansible.module_utils.basic. - Accept arguments, do work, return JSON.
Minimal example: library/hello_module.py:
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
def run_module():
module_args = dict(
name=dict(type='str', required=False, default='world')
)
result = dict(
changed=False,
message=''
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
name = module.params['name']
result['message'] = f"Hello, {name}!"
# No state change so changed=False
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()
Use it in a playbook:
- hosts: localhost
tasks:
- name: Test custom module
hello_module:
name: Ansible
register: result
- debug:
var: result
Key steps for real modules:
- Define
argument_specwith types, required/optional, choices. - Use
supports_check_modeto respect--checkwhere possible. - Determine whether something changed and set
changed=Trueappropriately. - Use
module.fail_json(msg="...")for errors. - Put shared code in
ansible.module_utilsfor reuse across modules.
For collections, modules live at plugins/modules/my_module.py and are referenced by FQCN: my_namespace.my_collection.my_module.
Ansible for Kubernetes
Ansible is not a Kubernetes-native tool like Helm, but it can:
- Apply manifests.
- Manage cluster resources (deployments, services, configmaps).
- Orchestrate around Kubernetes (create infra, update DNS, deploy apps).
Use kubernetes.core collection:
- hosts: localhost
gather_facts: false
collections:
- kubernetes.core
tasks:
- name: Apply manifest
k8s:
state: present
src: k8s/deployment.yml
Common patterns:
- Use Ansible to deploy Helm charts with
community.kubernetes.helm. - Use Ansible for environment bootstrapping and let Kubernetes handle the inner app lifecycle.
Security Hardening and Compliance
Ansible is great for repeatable hardening:
- Apply CIS Benchmarks, STIGs, etc.
- Ensure consistent OS baseline across fleets.
Typical approach:
- Use existing hardening roles like
devsec.hardening. - Customize vars for your baseline.
- Run in audit mode first (when supported), then enforce.
Example:
- hosts: linux
become: true
roles:
- devsec.hardening.os_hardening
Combine with:
ansible-playbook --checkfor dry-run.- Reports from custom callback plugins or CI artifacts.
Common Architecture Patterns and Real Use Cases
1. Golden Image + Ansible
- Build a base image (Packer) with OS + basic tools.
- Use Ansible to apply environment-specific config on boot.
2. Immutable Infrastructure with Ansible
- Use Terraform to create instances.
- Use Ansible to configure and deploy.
- Destroy and recreate instead of modifying in place.
3. Simple App Deployment
- Role for
nginxas reverse proxy. - Role for
app(deploy artifact, configure systemd). - Pipeline triggers Ansible to roll out new version.
4. Multi-environment Layout
inventories/
dev/
hosts.yml
group_vars/
stage/
prod/
roles/
nginx/
app/
playbooks/
site.yml
site.yml selects roles; inventory selects environment.
Common Gotchas and Misconceptions
- “Ansible is only for configuration” – not true. It also does orchestration and some provisioning.
- Mixing
shellfor everything instead of using modules – leads to fragile, non-idempotent setups. - Forgetting
become: trueand wondering why changes fail. - Relying on facts but disabling
gather_factswithout manually callingsetup. - Using
commandto edit config files instead oftemplate/lineinfile/blockinfile.
Example:
- name: Ensure facts available explicitly
setup:
End-to-End Example: Provision and Deploy a Simple App
Goal: configure a web server that serves a simple app behind nginx.
Inventory
inventory.ini:
[web]
web1.example.com ansible_user=ubuntu
Role: nginx
roles/nginx/tasks/main.yml:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: true
- name: Deploy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/app.conf
notify: Reload nginx
- name: Enable site
file:
src: /etc/nginx/sites-available/app.conf
dest: /etc/nginx/sites-enabled/app.conf
state: link
notify: Reload nginx
- name: Ensure default site disabled
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Reload nginx
roles/nginx/handlers/main.yml:
- name: Reload nginx
systemd:
name: nginx
state: reloaded
roles/nginx/templates/nginx.conf.j2:
server {
listen 80;
server_name {{ inventory_hostname }};
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
}
}
Role: app
roles/app/tasks/main.yml:
- name: Ensure app directory
file:
path: /opt/app
state: directory
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0755"
- name: Deploy app script
copy:
src: app.py
dest: /opt/app/app.py
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0755"
- name: Deploy systemd service
template:
src: app.service.j2
dest: /etc/systemd/system/app.service
notify: Restart app
- name: Ensure app running
systemd:
name: app
enabled: true
state: started
- name: Reload systemd daemon
systemd:
daemon_reload: true
when: ansible_service_mgr == "systemd"
roles/app/handlers/main.yml:
- name: Restart app
systemd:
name: app
state: restarted
Playbook
site.yml:
- hosts: web
become: true
vars:
app_user: deploy
app_port: 5000
roles:
- nginx
- app
Run:
ansible-playbook -i inventory.ini site.yml
You now have:
- App running on
localhost:5000. - Nginx proxying from port 80 to the app.
Testing: Molecule and Other Strategies
Molecule helps you test roles in isolation.
Initialize role with Molecule:
molecule init role myrole -d docker
cd myrole
molecule test
molecule/default/molecule.yml describes how to create instances (Docker, EC2, etc.) and run tests.
Typical flow:
molecule converge: apply role to test instance.molecule verify: run tests (often viatestinfraor similar).molecule destroy: tear down.
Combine this with CI to ensure your roles remain stable over time.
Terraform, Other Tools, and When Not to Use Ansible
Ansible is strong at config management and orchestration, weak at complex stateful infrastructure planning.
Good fits for Ansible:
- Configuring running servers.
- Application deployments.
- Rolling out OS patches.
- Simple provisioning and glue tasks.
Better handled by other tools:
- Complex infra graph (VPCs, subnets, peering, complex dependencies) → Terraform, CloudFormation, Pulumi.
- Kubernetes-native app lifecycle → Helm, ArgoCD, Flux.
- Continuous sync of desired state → GitOps tools.
Hybrid pattern (common and sane):
- Terraform builds infrastructure (instances, networking, security groups).
- Ansible configures OS and deploys applications.
Final Cheat Sheet
Quick reference of useful commands and concepts.
CLI
# Ad-hoc ping
ansible all -m ping -i inventory.ini
# Run a playbook
ansible-playbook -i inventory.ini site.yml
# Check syntax only
ansible-playbook site.yml --syntax-check
# Dry-run (check mode)
ansible-playbook site.yml --check
# Start at specific task
ansible-playbook site.yml --start-at-task "Install nginx"
# Show inventory graph
ansible-inventory -i inventory.yml --graph
Files and Layout
project/
ansible.cfg
inventory/
dev/
prod/
group_vars/
host_vars/
roles/
nginx/
app/
playbooks/
site.yml
Config (ansible.cfg)
[defaults]
inventory = inventory
remote_user = ubuntu
host_key_checking = False
retry_files_enabled = False
forks = 20
[ssh_connection]
pipelining = True
Vault
ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-playbook site.yml --ask-vault-pass
That’s the big-picture Ansible reference: from “what is this thing” all the way to writing your own modules and wiring it into CI/CD.