Ansible Basics

intermediate ansible playbooks roles configuration-management

Terraform creates infrastructure (servers, networks, databases). But once those servers exist, who installs software, copies config files, and starts services? That’s where Ansible comes in.

Ansible is a configuration management tool. It connects to our servers over SSH, runs tasks we define, and makes sure everything is set up the way we want. The best part — it’s agentless. We don’t need to install anything on the target machines. Just SSH access.

Ansible vs Terraform

These two are not competitors — they’re teammates.

Terraform
Creates infrastructure
Provisions VMs, networks, databases
Tracks state
Declarative (HCL)
Ansible
Configures infrastructure
Installs packages, copies files, starts services
Stateless (no state file)
Procedural (YAML playbooks)

Think of it like building a house: Terraform lays the foundation and builds the walls. Ansible paints, installs furniture, and sets up the Wi-Fi.

Inventory

The inventory is a file that lists the servers Ansible should manage:

# inventory.yml
all:
  children:
    webservers:
      hosts:
        web1:
          ansible_host: 10.0.1.10
        web2:
          ansible_host: 10.0.1.11
    databases:
      hosts:
        db1:
          ansible_host: 10.0.2.10

We group hosts so we can target them separately — “install nginx on webservers” or “update postgres on databases.”

Playbooks

A playbook is a YAML file that describes a series of tasks to run on specific hosts:

# setup-web.yml
- name: Setup web servers
  hosts: webservers
  become: true  # run as root (sudo)

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

    - name: Install nginx
      apt:
        name: nginx
        state: present  # make sure it's installed

    - name: Copy our nginx config
      copy:
        src: ./files/nginx.conf
        dest: /etc/nginx/nginx.conf
      notify: restart nginx  # trigger handler if file changed

    - name: Ensure nginx is running
      service:
        name: nginx
        state: started
        enabled: true  # start on boot

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

We run it with:

ansible-playbook -i inventory.yml setup-web.yml

Modules

Each task uses a module — a built-in action that Ansible knows how to perform. We used apt, copy, and service above. There are thousands of modules:

  • apt / yum — install packages
  • copy — copy files to remote hosts
  • template — copy files with variable substitution (Jinja2)
  • service — start/stop/restart services
  • user — create/manage system users
  • docker_container — manage Docker containers
  • git — clone repositories

Idempotency in Practice

Ansible modules are designed to be idempotent. If we say state: present for nginx and nginx is already installed, Ansible does nothing. If we say state: started for a service and it’s already running, nothing happens.

This means we can safely re-run a playbook multiple times. The output even shows us: ok (already good), changed (made a change), or failed.

Roles

As our playbooks grow, dumping everything into one file gets messy. Roles organize tasks, files, templates, and variables into a standard structure:

roles/
  nginx/
    tasks/main.yml      # the task list
    handlers/main.yml   # handlers (restart, reload)
    templates/           # Jinja2 template files
    files/               # static files to copy
    defaults/main.yml   # default variable values

Then our playbook becomes clean:

- name: Setup web servers
  hosts: webservers
  become: true
  roles:
    - nginx
    - certbot
    - app-deploy

Each role is self-contained and reusable. We can share roles via Ansible Galaxy, which is like npm but for Ansible roles.

In simple language, Ansible is the tool that SSHs into our servers and makes sure they’re configured exactly the way we described. No agents to install, no state to manage — just YAML files and SSH.