Overview

Ansible is an agentless configuration management tool. It connects to servers over SSH, runs tasks defined in YAML playbooks, and enforces the desired state of your infrastructure. This tutorial builds a playbook that installs Nginx, deploys a static site, and configures a firewall.

Why Ansible?

FeatureBenefit
AgentlessNo software to install on managed hosts; uses SSH
YAML syntaxHuman-readable, order-independent at the task level
IdempotentRe-running a playbook does not duplicate changes
Large module libraryBuilt-in support for packages, files, services, cloud APIs

Install Ansible

# Ubuntu
sudo apt update
sudo apt install ansible

# macOS
brew install ansible

# Via pip (any platform)
pip install ansible

See the Ansible installation guide for other platforms.

Core Concepts

TermDescription
InventoryList of managed hosts, grouped by role
PlaybookYAML file containing one or more plays
PlayMaps a group of hosts to a set of tasks
TaskA single action using a module
ModuleBuilt-in unit of work, such as apt or copy
RoleReusable bundle of tasks, files, and templates

Step 1: Create an Inventory

Create inventory.ini:

[webservers]
web1.example.com
web2.example.com

[webservers:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519

Test connectivity:

ansible -i inventory.ini webservers -m ping

Step 2: Write the Playbook

Create site.yml:

---
- name: Configure web servers
  hosts: webservers
  become: true

  vars:
    site_root: /var/www/example

  tasks:
    - name: Update apt cache
      apt:
        update_cache: true
        cache_valid_time: 3600

    - name: Install Nginx
      apt:
        name: nginx
        state: present

    - name: Create site directory
      file:
        path: "{{ site_root }}"
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'

    - name: Deploy index.html
      copy:
        src: files/index.html
        dest: "{{ site_root }}/index.html"
        owner: www-data
        group: www-data
        mode: '0644'

    - name: Enable Nginx service
      service:
        name: nginx
        state: started
        enabled: true

    - name: Allow HTTP through firewall
      ufw:
        rule: allow
        port: '80'
        proto: tcp

Step 3: Run the Playbook

ansible-playbook -i inventory.ini site.yml

Add --check to preview changes, or --diff to see file differences:

ansible-playbook -i inventory.ini site.yml --check --diff

Common Modules Reference

ModulePurpose
apt / yumInstall or remove packages
copyCopy files to managed hosts
templateRender Jinja2 templates
fileManage files, directories, and symlinks
service / systemdManage services
user / groupManage accounts
gitClone or update repositories
command / shellRun arbitrary commands

Using Variables and Handlers

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

  tasks:
    - name: Update Nginx config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx

A handler runs only when a task notifies it and reports a change.

Best Practices

  • Store playbooks in Git and run them through CI.
  • Use roles for anything reused across projects.
  • Prefer --check mode before applying to production.
  • Encrypt secrets with ansible-vault.
  • Keep inventory separate from playbooks so environments can share code.