Ansible Tutorial: Agentless Config Management
Ansible automates server configuration, app deployment, and orchestration over plain SSH - no agents to install on the machines you manage.
What is Ansible?
Ansible is an open-source automation tool used for configuration management, application deployment, and orchestration across fleets of servers. Instead of logging into each machine and running commands by hand, you describe the desired state of your systems in YAML files called playbooks, then run them against an inventory of hosts. Ansible connects to each host, figures out what needs to change, and makes only those changes.
Its defining trait is that it is agentless. Tools like Puppet and Chef traditionally run a persistent agent on every managed node. Ansible instead connects over standard SSH (or WinRM for Windows targets), pushes small modules to the remote machine, executes them with the remote Python interpreter, and cleans up afterward. The only requirements on a target are SSH access and Python. This keeps the attack surface small and makes Ansible easy to adopt on machines you do not fully control.
Ansible is also declarative and idempotent. You declare that a package should be present, a config file should contain certain content, or a service should be running and enabled. Running the same playbook twice does not double-apply the change - if the system already matches the desired state, Ansible reports "ok" and moves on. That property is what makes it safe to run playbooks repeatedly as your source of truth.
How Ansible works (key concepts)
A handful of concepts cover most of what you will use day to day:
- Control node: the machine where Ansible is installed and where you run commands. It can be your laptop or a CI runner. Windows is not supported as a control node.
- Managed nodes: the servers Ansible configures. They need only SSH and Python, never Ansible itself.
- Inventory: a file (INI or YAML) listing your hosts, optionally organized into groups like
webserversordb. Variables can be attached to hosts or groups. - Modules: the units of work, such as
ansible.builtin.apt,ansible.builtin.copy, oransible.builtin.service. Each module is idempotent and reports whether it changed anything. - Tasks: a single call to a module with arguments, run in order against the targeted hosts.
- Playbooks: ordered lists of plays, where each play maps a group of hosts to a list of tasks. This is the main artifact you write and version-control.
- Roles: a directory structure for packaging tasks, variables, templates, and handlers into reusable units.
- Handlers: tasks that run only when notified by another task, typically to restart a service after its config changed.
- Collections: distributable bundles of modules, roles, and plugins. The
ansiblepackage ships many; you can install more from Ansible Galaxy.
Installing Ansible
Ansible runs on Python 3. On modern Linux distributions that enforce PEP 668, installing into the system Python with pip is blocked, so use pipx for an isolated, globally available install. This is the cleanest method on Ubuntu, Debian, and Fedora.
# Install pipx, then Ansible (includes ansible-core + community collections)
sudo apt update
sudo apt install -y pipx
pipx ensurepath
pipx install ansible
# Confirm the install
ansible --version
If you prefer a minimal footprint, or want to manage the Python environment yourself, install into a virtual environment. You can install the full ansible package or just ansible-core:
python3 -m venv ~/ansible-venv
source ~/ansible-venv/bin/activate
# Full distribution (engine + bundled collections)
pip install ansible
# Or the engine only (add ==<version> to pin a specific release)
pip install ansible-core
On Ubuntu you can also use the official PPA, which tracks the full ansible package:
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
Hands-on: configure a web server with a playbook
Start with an inventory file describing your hosts. Create inventory.ini:
[webservers]
web1 ansible_host=192.0.2.10
web2 ansible_host=192.0.2.11
[webservers:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
Before writing a playbook, verify connectivity with an ad-hoc command. The ping module does not use ICMP - it confirms SSH login and a working Python interpreter on each host:
ansible webservers -i inventory.ini -m ansible.builtin.ping
Now write site.yml to install nginx, copy in a config from a template, and ensure the service is running. The template task expects a Jinja2 file at templates/site.conf.j2 next to the playbook - put your nginx server block there. The handler restarts nginx only when the rendered config changes, which is the idempotent pattern you want:
---
- name: Configure web servers
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy nginx site config
ansible.builtin.template:
src: templates/site.conf.j2
dest: /etc/nginx/sites-available/default
owner: root
group: root
mode: "0644"
notify: Restart nginx
- name: Ensure nginx is running and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
Run it with the inventory you defined. Use --check first for a dry run that reports what would change without touching the hosts:
ansible-playbook -i inventory.ini site.yml --check
ansible-playbook -i inventory.ini site.yml
The first real run shows tasks as changed. Run the same command again and the same tasks report ok with nothing changed - that is idempotency in action, and it shows your playbook can serve as a reliable source of truth.
When to use Ansible
Ansible works best when you need to bring many machines to a known, repeatable state without running an agent on each one. Typical fits include provisioning a set of identical web or database servers, rolling out application deployments, applying security baselines and patches across a fleet, and orchestrating multi-step releases that touch several tiers in order. Because it is push-based and agentless, it is a good fit for environments where you cannot or do not want to maintain persistent daemons on every target.
It is less suited to scenarios that demand continuous enforcement of state without an operator triggering a run - a pull-based agent model or a dedicated drift-detection loop fits those better. For provisioning cloud infrastructure itself, a declarative infrastructure-as-code tool like Terraform is usually the better primary choice, with Ansible handling the in-instance configuration afterward. A common and effective pattern is to pair the two: Terraform creates the servers, then Ansible configures what runs on them.
Frequently asked questions
What is Ansible?
Ansible is an open-source automation tool for configuration management, application deployment, and orchestration. It connects to managed nodes over SSH (or WinRM for Windows) and applies declarative YAML playbooks. It is agentless, so the only requirements on a target are SSH access and Python.
Is Ansible agentless?
Yes. Ansible pushes small modules to managed nodes over SSH, runs them with Python, and removes them when finished. There is no long-running daemon or agent to install, patch, or secure on the targets - only the control node needs Ansible installed.
What is the difference between ansible and ansible-core?
ansible-core is the engine: the CLI, the execution model, and a small set of built-in modules. The ansible package bundles ansible-core plus dozens of community collections. Install ansible-core if you want a minimal footprint and add collections yourself.
What language are Ansible playbooks written in?
Playbooks are written in YAML and use Jinja2 templating for variables and expressions. You do not write imperative scripts - you declare the desired state of files, packages, and services, and Ansible figures out the changes needed.
Do real production work, free. Live cloud workspaces in your browser - every project you complete lands on your portfolio.
Get started - free