Ansible
Ansible automates server and service configuration with YAML playbooks and idempotent modules. Python teams use it for bootstrapping hosts, deploying apps, and enforcing baseline OS hardening without maintaining imperative shell scripts.
Recipe
Quick-reference recipe card - copy-paste ready.
# playbook.yml
- hosts: web
become: true
tasks:
- name: Ensure app directory exists
ansible.builtin.file:
path: /opt/myapp
state: directory
mode: "0755"
- name: Deploy config
ansible.builtin.template:
src: templates/app.env.j2
dest: /opt/myapp/.env
mode: "0600"ansible-playbook -i inventory.ini playbook.yml --check
ansible-playbook -i inventory.ini playbook.ymlWhen to reach for this:
- OS and package configuration on VMs or bare metal
- Agentless SSH workflows without installing daemons on targets
- Ad-hoc convergence when you need
changed=0idempotency reports - Glue between IaC and apps - cloud creates instances, Ansible configures them
- Small teams wanting YAML over a full Pulumi/Terraform program
Working Example
A minimal project with inventory, role, and Jinja2 template deployed via ansible-playbook.
# inventory.ini
[web]
web1 ansible_host=127.0.0.1 ansible_connection=local# site.yml
- hosts: web
roles:
- myapp# roles/myapp/tasks/main.yml
- name: Create app user
ansible.builtin.user:
name: myapp
system: true
shell: /usr/sbin/nologin
- name: Install Python venv package (Debian family)
ansible.builtin.apt:
name: python3-venv
state: present
become: true
- name: Deploy environment file
ansible.builtin.template:
src: app.env.j2
dest: /etc/myapp.env
owner: root
group: myapp
mode: "0640"# roles/myapp/templates/app.env.j2
APP_ENV={{ app_env | default('dev') }}
LOG_LEVEL={{ log_level | default('info') }}ansible-playbook -i inventory.ini site.yml -e app_env=prod -e log_level=warningWhat this demonstrates:
- Inventory groups hosts;
ansible_connection=localruns on your machine for demos - Roles bundle tasks, templates, and defaults into reusable units
ansible.builtin.templaterenders Jinja2 with variables from-eor group_vars- Modules report
changedonly when state actually differs
Deep Dive
How It Works
- Ansible connects over SSH (or local) and pushes task modules to the target
- Each module compares desired state (YAML args) to actual state and converges
- Facts are gathered once per play unless
gather_facts: false - Handlers run at the end of the play, only if notified by a changed task
- Vault encrypts secrets in repo; decrypt at runtime with
--ask-vault-passor key file
Core Concepts
| Concept | Purpose |
|---|---|
| Inventory | Host groups and connection vars |
| Playbook | Ordered plays and tasks |
| Role | Reusable task/template bundle |
| Module | Idempotent unit of work (apt, file, service) |
| Handler | Restart service only when config changed |
Python Notes
# Call Ansible from Python via ansible-runner (optional orchestration)
import ansible_runner
result = ansible_runner.run(
playbook="site.yml",
inventory="inventory.ini",
extravars={"app_env": "staging"},
)
assert result.status == "successful", result.stdout.read()Gotchas
- Using
shellfor everything - loses idempotency andchangedreporting. Fix: find the matchingansible.builtin.*module first. - Secrets in plain group_vars - credentials land in git history. Fix:
ansible-vault encrypt_stringor external secret store lookup plugins. - Running without
--checkfirst - surprises on shared hosts. Fix: dry-run in CI and require approval for prod plays. - Unpinned collections -
ansible-galaxy collection installpulls latest and breaks playbooks. Fix: pin versions inrequirements.yml. - Become password on CI - interactive prompts fail pipelines. Fix: passwordless sudo for deploy user or use AWX/Tower with stored creds.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Pulumi/Terraform | Cloud resource lifecycle | Only need OS package config on existing VMs |
| Fabric/paramiko scripts | One-off remote commands | You need idempotent convergence reports |
| cloud-init | First-boot only on new instances | Ongoing config drift correction |
| Salt/Chef | Large fleet with agents acceptable | You want agentless SSH simplicity |
FAQs
Why Ansible instead of shell scripts over SSH?
Modules compare desired vs actual state and report changed counts. Shell scripts rerun blindly and hide drift.
Can I manage AWS resources with Ansible?
Yes - use amazon.aws collection modules (ec2_instance, s3_bucket). For complex VPC topologies, pair Terraform/Pulumi for cloud and Ansible for host config.
What is the difference between a play and a role?
A play targets host groups and lists tasks or roles. A role is a reusable folder of tasks, handlers, templates, and defaults included by plays.
How do I test playbooks locally?
Use ansible_connection=local in inventory or Molecule with Docker/Podman drivers to converge ephemeral test instances.
When should handlers run?
Handlers execute once at the end of the play, only if a task notified them and reported changed. Use for service restarts after config updates.
How do I pass environment-specific values?
Use group_vars/<group>.yml, host_vars/, or -e key=value at runtime. Keep structure identical across envs, change values only.
Does Ansible require Python on the target?
Most modules need Python on the remote host (preinstalled on mainstream Linux). Raw command/shell modules work without it but lose idempotency.
How do I speed up large inventories?
Enable SSH pipelining, use strategy: free for independent hosts, and limit fact gathering with gather_subset when full facts are unnecessary.
Can I run Ansible from a Python 3.14 venv?
Yes - install ansible into your project venv with uv pip install ansible and invoke ansible-playbook from that environment for reproducible versions.
What is check mode?
--check simulates changes without applying them. Pair with --diff to preview file content changes before prod runs.
Related
- Infrastructure Automation Basics - IaC mindset and idempotency
- Configuration Management - templating and secrets
- Provisioning Cloud Resources - cloud resource lifecycle
- Testing Infrastructure Code - Molecule and policy checks
- Infrastructure Automation Best Practices - auditable automation rules
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.