Ansible Fundamentals: Core Concepts with Simple Examples

Ansible is not magic, and it is not complicated. It is a declarative automation tool that answers one question:

“What should the system look like when I’m done?”

In two articles, you’ll learn the core concepts of Ansible and apply them through small, practical tasks.

We explain how to set up a fully functional WordPress website using Ansible to install and configure everything you need: PHP, a database, and a web server.

First things first. Let’s understand how Ansible works and learn how to use it with simple tasks.

1. What Ansible Is (and Is Not)

What Ansible is

  • Agentless automation tool (no daemon on target machines)
  • Uses SSH (or WinRM for Windows)
  • Written in Python, configured in YAML
  • Focused on idempotency (safe to run multiple times)

What Ansible is not

  • Not a configuration language like Bash
  • Not a replacement for Docker or Kubernetes
  • Not a CI/CD system (but integrates well with them)

Key idea:
Ansible describes the desired state, not step-by-step instructions.

2. The Ansible Execution Model (Big Picture)

When you run Ansible:

  1. You run ansible or ansible-playbook from your machine
  2. Ansible connects to target hosts via SSH
  3. It runs modules remotely
  4. Modules report whether they changed anything
  5. Ansible prints a summary

No agent. No polling. No persistent connection.

3. Inventory: “Which machines do I manage?”

The inventory defines where Ansible runs.

Minimal inventory (INI format)

[servers]
server1 ansible_host=192.168.1.10 ansible_user=deploy

YAML inventory (recommended)

all:
  hosts:
    server1:
      ansible_host: 192.168.1.10
      ansible_user: deploy
      ansible_port: 2222

Important ideas:

  • Inventory is data, not code
  • You can attach variables to hosts and groups
  • Inventory can be static (files) or dynamic (cloud APIs)

Mini-task

Run:

ansible all -i inventory.yml -m ping

If you see pong, Ansible works.

4. Modules: The Building Blocks

Ansible does almost nothing itself.
It calls modules.

Examples:

  • apt → install packages
  • user → manage users
  • service → start/stop services
  • copy → copy files
  • template → generate config files

Example: install a package

ansible all -m apt -a "name=vim state=present" --become

Why modules matter

  • They are idempotent
  • They know how to check the current state
  • They return structured results

Bad mindset:

shell: apt install vim -y

Good mindset:

apt:
  name: vim
  state: present

5. Playbooks: Describing Desired State

A playbook is a list of plays.
A play map:

hosts → tasks

Minimal playbook

- name: Install basic tools
  hosts: all
  become: true
  tasks:
    - name: Install vim
      apt:
        name: vim
        state: present

Run it:

ansible-playbook site.yml

Rerun it.
Notice: no changes → idempotency.

6. Tasks: Small, Explicit Actions

A task is:

  • One module call
  • With clear intent
  • With a descriptive name

Good task

- name: Install curl
  apt:
    name: curl
    state: present

Bad task

- name: Do stuff
  shell: |
    apt update
    apt install curl -y

Rule of thumb:
If a task name doesn’t read like English, rewrite it.

7. Variables: Making Playbooks Reusable

Hardcoding values is how automation dies.

Variables in a playbook

vars:
  package_name: curl
- name: Install package
  apt:
    name: "{{ package_name }}"
    state: present

Variables from the inventory

server1:
  ansible_host: 192.168.1.10
  app_user: deploy

Variables allow:

  • Environment differences (dev/prod)
  • Reusable roles
  • Cleaner templates

8. Handlers: Reacting to Change

Handlers run only if notified.

Example

- name: Copy config
  copy:
    src: app.conf
    dest: /etc/app.conf
  notify: Restart app

handlers:
  - name: Restart app
    service:
      name: app
      state: restarted

Key insight:
Handlers prevent unnecessary restarts.

9. Templates: Config Files Done Right

Templates use Jinja2.

Template (nginx.conf.j2)

server {
  listen {{ port }};
  server_name {{ domain }};
}

Task

template:
  src: nginx.conf.j2
  dest: /etc/nginx/nginx.conf

This is how you avoid:

  • Copy-paste configs
  • Environment-specific files
  • Manual edits on servers

10. Roles: Scaling Without Chaos

When playbooks grow, you need structure.

A role is a self-contained unit:

roles/
  web/
    tasks/
    handlers/
    templates/
    defaults/

Roles allow:

  • Reuse
  • Testing
  • Clear ownership
  • Clean playbooks

Playbook with roles

- hosts: all
  roles:
    - web
    - database

This is how real teams use Ansible.

11. Idempotency: The Core Principle

Idempotency means:

Running the same playbook multiple times leads to the same result.

Why it matters:

  • Safe automation
  • Easy recovery
  • Predictable deployments

Ansible success metric:
You can run your playbook anytime without fear.

12. How to Think in Ansible (Mental Shift)

❌ “How do I do this?”
✅ “What should exist?”

❌ “Run this command.”
✅ “Ensure this state.”

❌ “One big script.”
✅ “Many small, clear tasks.”

If you adopt this mindset, Ansible becomes simple.

13. Small Practice Assignment (Before the Next Article)

Do this before reading the WordPress HOW-TO:

  1. Create an inventory with one Debian host
  2. Write a playbook that:
    • Updates apt cache
    • Installs curl, vim, htop
    • Ensures lighttpd is installed (not configured yet)
  3. Add a handler that restarts lighttpd only if needed
  4. Run the playbook twice

If this feels boring — good.
That means automation is working.

What’s Next

In the following article, we’ll apply everything you learned here to a real, production-style task:

Automatically installing WordPress on Debian using
lighttpd + MariaDB + PHP-FPM — cleanly, reproducibly, and safely.

Leave a Reply

Your email address will not be published. Required fields are marked *