Ansible is an open-source IT automation engine that configures servers, deploys applications, and orchestrates multi-node workflows over plain SSH, with no agent or daemon installed on the machines it manages. For server automation it is popular because it is agentless, push-based, and idempotent: you describe the desired state of a server in a YAML playbook, and Ansible makes only the changes needed to reach that state, so the same playbook can be re-run safely as often as you like.
Key takeaways
- Ansible is agentless: it connects over SSH (or WinRM for Windows), so a target server needs nothing but Python and an SSH login, not a pre-installed daemon.
- Playbooks are declarative YAML describing desired state, and idempotency means re-running them is safe and changes nothing once a server already matches.
- Modern Ansible ships as ansible-core 2.16+ plus the Ansible community package; extra modules come from collections installed with
ansible-galaxy collection install. - Always reference modules by their fully-qualified collection name (FQCN), e.g.
ansible.builtin.apt,ansible.builtin.copy,ansible.builtin.service, never the deprecated bare names likecopy:orservice:. - The core building blocks are the inventory (which hosts), playbooks (what to do), roles (reusable bundles), handlers (event-driven tasks), and ansible-vault (encrypted secrets).
- Ansible reduces server provisioning from a manual, error-prone checklist to a versioned, repeatable run, often cutting setup time by 80% or more.
What is Ansible and how does it work?
Ansible runs from a single control node (your laptop, a CI runner, or a bastion host) and pushes configuration out to managed nodes. There is no central master to maintain and nothing to pre-install on the targets: Ansible copies small modules to each host over SSH, executes them with Python, and cleans them up afterwards.
The workflow is deliberately simple:
- Define your servers in an inventory (INI or YAML).
- Describe the desired state in a playbook (YAML).
- Run
ansible-playbookfrom the control node.
Because each module checks current state before acting, a task such as "ensure nginx is installed and running" does nothing on a host where that is already true. This idempotency is what makes Ansible safe for repeatable server automation, unlike a one-shot shell script that blindly re-runs every command on each invocation.
How do you install Ansible in 2026?
Install ansible-core (the engine plus the ansible.builtin modules) or the larger Ansible community package (ansible-core plus a curated set of collections). On most systems pip into a virtualenv is the cleanest, most current path; distribution packages tend to lag several releases behind.
# Recommended: install ansible-core with pip
python3 -m pip install --user ansible-core
# Or the full community package (core + popular collections)
python3 -m pip install --user ansible
# Add modules on demand from Ansible Galaxy
ansible-galaxy collection install community.general
ansible-galaxy collection install ansible.posix
# Confirm the versions and installed collections
ansible --version
ansible-galaxy collection listPin the version you test against (for example ansible-core==2.16.*) in a requirements.txt, and capture extra collections in a requirements.yml so every teammate and CI runner installs an identical toolchain. For a deeper tour of collections and the wider ecosystem, see our introduction to Ansible Galaxy.
Inventory and ansible.cfg: telling Ansible about your servers
The inventory lists the hosts you manage and groups them so a playbook can target, say, all webservers at once. Keep a project-local inventory under version control rather than editing the global /etc/ansible/hosts. A small ansible.cfg in your project root makes the defaults explicit and portable. Inventories can be written in INI or YAML; the INI form below is the quickest to read.
# inventory.ini -- a project-local inventory in INI format
[webservers]
web1.example.com
web2.example.com
[dbservers]
db1.example.com ansible_host=10.0.0.21
# group_vars applied to every host
[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3A minimal ansible.cfg keeps repeated flags out of your commands:
# ansible.cfg
[defaults]
inventory = ./inventory.ini
host_key_checking = False
retry_files_enabled = False
interpreter_python = auto_silentWriting an idempotent playbook with FQCN modules and handlers
A playbook maps groups of hosts to an ordered list of tasks. The example below installs and configures nginx on every host in the webservers group. Note three modern conventions: every module uses its FQCN (ansible.builtin.*), state is declarative (state: present, enabled: true), and a handler restarts nginx only when the config file actually changes, so a no-op run stays a no-op.
---
# site.yml -- idempotent web server setup
- name: Configure web servers
hosts: webservers
become: true
vars:
server_name: example.com
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy site configuration
ansible.builtin.template:
src: templates/site.conf.j2
dest: /etc/nginx/sites-available/{{ server_name }}.conf
owner: root
group: root
mode: '0644'
notify: Restart nginx
- name: Enable the site
ansible.builtin.file:
src: /etc/nginx/sites-available/{{ server_name }}.conf
dest: /etc/nginx/sites-enabled/{{ server_name }}.conf
state: link
notify: Restart nginx
- name: Ensure nginx is started and enabled on boot
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restartedRun the playbook from the control node. Use check mode (--check --diff) first to preview changes without touching the servers, then apply for real:
# Dry-run: preview every change without applying it
ansible-playbook -i inventory.ini site.yml --check --diff
# Apply for real
ansible-playbook -i inventory.ini site.yml
# Limit to one host and raise verbosity while debugging
ansible-playbook -i inventory.ini site.yml --limit web1.example.com -vvRun it twice. The first run reports changed= counts; the second should report changed=0, the signature of a correctly idempotent playbook.
Playbooks, roles, and handlers: how a project grows
A handful of tasks fits in a single playbook, but real server fleets outgrow that quickly. Roles package tasks, handlers, templates, files, and default variables into a reusable directory structure so the same "nginx" or "postgres" setup can be dropped into any project. Scaffold one with ansible-galaxy init roles/nginx, then include it from a play.
- Handlers are tasks that run only when notified by a changed task, ideal for restarts and reloads.
- Variables flow from
group_vars/,host_vars/, role defaults, and the command line, with a well-defined precedence order. - Templates use Jinja2 (
{{ variable }}) to render config files per host.
Once your roles are stable, Ansible slots naturally into a deployment pipeline. See our walkthroughs on deploying Django with uWSGI and nginx via an Ansible playbook, automating Django deployments with a fabfile, and wiring it all into CI/CD with GitLab and Docker.
Managing secrets with ansible-vault
Never commit plaintext passwords, API keys, or TLS private keys. ansible-vault encrypts variable files (and even single values) with AES-256, and Ansible decrypts them in memory only at run time. Keep secrets in a dedicated group_vars/all/vault.yml and reference them from ordinary variables.
# Encrypt a variables file containing secrets
ansible-vault encrypt group_vars/all/vault.yml
# Edit it later (decrypts in memory only)
ansible-vault edit group_vars/all/vault.yml
# Encrypt a single string for pasting into a vars file
ansible-vault encrypt_string SuperSecret123 --name db_password
# Run a playbook that needs the vault password
ansible-playbook -i inventory.ini site.yml --ask-vault-passAnsible vs Terraform vs shell scripts vs Puppet/Chef
Ansible is not the only automation tool, and the choice depends on whether you are provisioning infrastructure or configuring it. The table below compares the common options for server automation in 2026.
| Tool | Agentless? | Language | Model | Idempotent? | Best for | Learning curve |
|---|---|---|---|---|---|---|
| Ansible | Yes (SSH/WinRM) | YAML + Jinja2 | Push | Yes | Config management, app deploys, ad-hoc orchestration | Low |
| Terraform | Yes (provider APIs) | HCL | Push (plan/apply) | Yes (state-based) | Provisioning cloud infrastructure | Medium |
| Shell scripts | Yes | Bash/sh | Push | No (manual) | Quick one-off tasks | Easy to write, hard to maintain |
| Puppet / Chef | No (agent) | Puppet DSL / Ruby | Pull | Yes | Large fleets with central policy enforcement | High |
| SaltStack | Optional | YAML + Python | Push and pull | Yes | High-scale, event-driven automation | Medium |
In practice Ansible and Terraform are complementary, not rivals: Terraform creates the servers, networks, and load balancers, then Ansible configures the operating system and deploys the application onto them. Shell scripts are fine for a throwaway command, but they lack idempotency and become brittle the moment they grow.
Where does Ansible fit in a containerized world?
Containers did not kill configuration management; they shifted some of it into image builds. Ansible still earns its place: it provisions the Docker or Kubernetes hosts themselves, manages the servers that sit outside your cluster (databases, bastions, load balancers), bootstraps CI runners, and can even build images. If your stack is container-first, pair this with our guide on deploying a Django project into a Docker container to see where each tool draws the line.
Frequently Asked Questions
Is Ansible agentless, and what do managed servers need?
Yes. Ansible is agentless: it does not install a persistent daemon on the machines it manages. It connects over SSH (or WinRM for Windows) from a control node, copies small modules to the target, runs them with Python, and removes them afterwards. A managed Linux server therefore needs only an SSH login and a working Python interpreter; nothing else has to be installed ahead of time.
What does idempotency mean in Ansible?
Idempotency means a task produces the same end state no matter how many times you run it. An idempotent Ansible task checks the current state of the server before acting, so "ensure nginx is installed" installs nginx only if it is missing and reports no change otherwise. A correctly written playbook reports changed=0 on its second consecutive run, which is the practical test that it is idempotent and safe to re-apply.
What is FQCN and why should I use ansible.builtin module names?
FQCN stands for fully-qualified collection name, the namespace.collection.module form such as ansible.builtin.copy, ansible.builtin.apt, or ansible.builtin.service. Since the content was split into collections, bare names like copy: or service: are deprecated and can resolve to the wrong module when multiple collections are installed. Always using the FQCN makes playbooks explicit, portable, and forward-compatible with ansible-core 2.16 and later.
What is the difference between a playbook, a role, and a collection?
A playbook is a YAML file that maps hosts to an ordered list of tasks. A role is a reusable, structured bundle of tasks, handlers, templates, files, and default variables that you include from a playbook to avoid repetition. A collection is a distributable package of modules, plugins, and roles, installed with ansible-galaxy collection install; for example community.general and ansible.posix are collections that extend the built-in module set.
Ansible vs Terraform: which should I use for server automation?
Use Terraform to provision infrastructure (create the VMs, networks, and load balancers) and Ansible to configure it (install packages, manage services, deploy your application). They are complementary rather than competing: Terraform tracks declarative state for cloud resources, while Ansible excels at agentless, push-based configuration and orchestration over SSH. Many teams run Terraform first, then hand the resulting hosts to an Ansible playbook.
How do I store passwords and API keys safely in Ansible?
Use ansible-vault, which encrypts variable files or individual values with AES-256 and decrypts them in memory only at run time. Keep secrets in a dedicated encrypted file such as group_vars/all/vault.yml, commit only the encrypted form, and supply the password at run time with --ask-vault-pass or a vault password file kept outside version control. This keeps credentials out of plaintext while remaining fully automatable in CI.
Get expert help with Ansible and server automation
MicroPyramid has been automating infrastructure for clients since 2014, with 50+ delivered projects across startups and enterprises. If you want repeatable, idempotent provisioning, hardened configuration, and zero-downtime deploys without staffing a full platform team, our server maintenance and DevOps services cover Ansible playbooks, roles, and pipelines end to end. Planning a move to the cloud at the same time? We pair this with AWS consulting and cloud migration services so provisioning, configuration, and migration are designed together rather than bolted on later.