Introduction

Ansible automates server configuration, application deployment, and infrastructure management. You write YAML playbooks describing the desired state, and Ansible makes it happen — no agents to install, no complex setup. This guide takes you from zero to automating real infrastructure.

What Is Ansible?

Ansible is an agentless automation tool. It connects to your servers via SSH (or WinRM for Windows), runs tasks, and disconnects. No software to install on managed hosts.

Key benefits:

  • Agentless — uses SSH, nothing to install on remote hosts
  • YAML-based — human-readable configuration
  • Idempotent — safe to run multiple times (same result)
  • Batteries included — 3,000+ modules for every use case
  • Free and open source — backed by Red Hat

Install Ansible

# Ubuntu/Debian
sudo apt update
sudo apt install ansible -y

# RHEL/CentOS/Fedora
sudo dnf install ansible-core -y

# macOS
brew install ansible

# pip (any OS)
pip install ansible

# Verify
ansible --version

Core Concepts

┌─────────────────────────────────────────┐
│                PLAYBOOK                 │
│  (YAML file describing desired state)   │
│                                         │
│  ┌─────────┐  ┌─────────┐             │
│  │  Play 1  │  │  Play 2  │            │
│  │ hosts:   │  │ hosts:   │            │
│  │ web      │  │ db       │            │
│  │          │  │          │            │
│  │ Task 1   │  │ Task 1   │            │
│  │ Task 2   │  │ Task 2   │            │
│  └─────────┘  └─────────┘             │
└──────────────────┬──────────────────────┘
                   │
           ┌───────┴───────┐
           │   INVENTORY   │
           │ (host list)   │
           │               │
           │ web-01        │
           │ web-02        │
           │ db-01         │
           └───────────────┘
ConceptDescription
Control NodeMachine where Ansible runs (your laptop/CI server)
Managed NodeRemote server being managed
InventoryList of managed hosts
PlaybookYAML file with automation tasks
PlayMaps hosts to tasks
TaskSingle action (install package, copy file, etc.)
ModuleUnit of code Ansible executes (apt, copy, service)
RoleReusable bundle of tasks, templates, files
HandlerTask triggered by notifications (e.g., restart service)

Step 1: Create Inventory

# inventory.ini
[webservers]
web-01 ansible_host=192.168.1.10
web-02 ansible_host=192.168.1.11

[dbservers]
db-01 ansible_host=192.168.1.20

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

Or YAML format:

# inventory.yml
all:
  children:
    webservers:
      hosts:
        web-01:
          ansible_host: 192.168.1.10
        web-02:
          ansible_host: 192.168.1.11
    dbservers:
      hosts:
        db-01:
          ansible_host: 192.168.1.20
  vars:
    ansible_user: deploy

Step 2: Test Connection

# Ping all hosts
ansible -i inventory.ini all -m ping

# Expected output:
# web-01 | SUCCESS => { "ping": "pong" }
# web-02 | SUCCESS => { "ping": "pong" }
# db-01  | SUCCESS => { "ping": "pong" }

# Run ad-hoc command
ansible -i inventory.ini webservers -m command -a "uptime"

Step 3: Write Your First Playbook

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

  tasks:
    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 3600

    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present

    - name: Copy website
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
        mode: "0644"

    - name: Start nginx
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true
# Run it
ansible-playbook -i inventory.ini playbook.yml

Step 4: Variables

---
- name: Deploy application
  hosts: webservers
  become: true
  vars:
    app_name: myapp
    app_port: 8080
    app_user: deploy

  tasks:
    - name: Create app user
      ansible.builtin.user:
        name: "{{ app_user }}"
        shell: /bin/bash

    - name: Create app directory
      ansible.builtin.file:
        path: "/opt/{{ app_name }}"
        state: directory
        owner: "{{ app_user }}"
        mode: "0755"

    - name: Show config
      ansible.builtin.debug:
        msg: "{{ app_name }} will run on port {{ app_port }}"

Step 5: Templates

- name: Deploy nginx config
  ansible.builtin.template:
    src: templates/nginx.conf.j2
    dest: /etc/nginx/sites-available/myapp
  notify: Reload nginx

handlers:
  - name: Reload nginx
    ansible.builtin.service:
      name: nginx
      state: reloaded
# templates/nginx.conf.j2
server {
    listen {{ app_port }};
    server_name {{ domain }};
    
    location / {
        proxy_pass http://127.0.0.1:{{ backend_port }};
    }
}

Step 6: Conditionals and Loops

# Conditionals
- name: Install on Debian
  ansible.builtin.apt:
    name: nginx
  when: ansible_os_family == "Debian"

- name: Install on RHEL
  ansible.builtin.yum:
    name: nginx
  when: ansible_os_family == "RedHat"

# Loops
- name: Create users
  ansible.builtin.user:
    name: "{{ item }}"
    state: present
  loop:
    - alice
    - bob
    - carol

# Loop with dict
- name: Install packages
  ansible.builtin.apt:
    name: "{{ item.name }}"
    state: "{{ item.state }}"
  loop:
    - { name: nginx, state: present }
    - { name: apache2, state: absent }

Step 7: Roles

# Create role structure
ansible-galaxy init roles/webserver
roles/webserver/
├── defaults/main.yml     # Default variables
├── handlers/main.yml     # Handlers
├── tasks/main.yml        # Tasks
├── templates/            # Jinja2 templates
├── files/                # Static files
└── meta/main.yml         # Dependencies
# roles/webserver/tasks/main.yml
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present

- name: Deploy config
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: Reload nginx
# Use the role
---
- hosts: webservers
  become: true
  roles:
    - webserver

Step 8: Ansible Configuration

# ansible.cfg (in project root)
[defaults]
inventory = inventory.yml
remote_user = deploy
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml

[privilege_escalation]
become = True
become_method = sudo

Complete Example: Deploy a Web App

---
- name: Deploy web application
  hosts: webservers
  become: true
  vars:
    app_version: "2.1.0"
    app_user: deploy
    app_dir: /opt/myapp
    app_port: 8080

  tasks:
    - name: Install dependencies
      ansible.builtin.apt:
        name: [nginx, python3, python3-pip]
        state: present
        update_cache: true

    - name: Create app user
      ansible.builtin.user:
        name: "{{ app_user }}"
        system: true
        shell: /usr/sbin/nologin

    - name: Create app directory
      ansible.builtin.file:
        path: "{{ app_dir }}"
        state: directory
        owner: "{{ app_user }}"

    - name: Deploy application
      ansible.builtin.unarchive:
        src: "https://releases.example.com/myapp-{{ app_version }}.tar.gz"
        dest: "{{ app_dir }}"
        remote_src: true
        owner: "{{ app_user }}"
      notify: Restart app

    - name: Deploy systemd service
      ansible.builtin.template:
        src: myapp.service.j2
        dest: /etc/systemd/system/myapp.service
      notify: Restart app

    - name: Deploy nginx reverse proxy
      ansible.builtin.template:
        src: nginx-proxy.conf.j2
        dest: /etc/nginx/sites-enabled/myapp
      notify: Reload nginx

    - name: Start services
      ansible.builtin.service:
        name: "{{ item }}"
        state: started
        enabled: true
      loop: [nginx, myapp]

  handlers:
    - name: Restart app
      ansible.builtin.systemd:
        name: myapp
        state: restarted
        daemon_reload: true

    - name: Reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

Next Steps

TopicArticle
Playbooks in depthAnsible Playbook Guide
Organize with rolesAnsible Roles Guide
Manage secretsAnsible Vault Guide
Template filesAnsible Jinja2 Templates
Test your rolesAnsible Molecule Testing
Best practicesAnsible Best Practices
PerformanceSpeed Up Ansible
WindowsAnsible Windows Guide
CloudAnsible AWS Guide
ErrorsAnsible Troubleshooting

Conclusion

You've learned the fundamentals: inventory, playbooks, variables, templates, conditionals, loops, roles, and handlers. Ansible's power comes from its simplicity — YAML files describing what you want, and modules that make it happen idempotently. Start small (one server, one task), build up to roles, then scale to your entire infrastructure. The 3,000+ built-in modules mean there's almost nothing Ansible can't automate.