Operations 20 min read

Mastering Ansible: Complete Guide to ansible.cfg, Inventory, and Playbooks

This article walks through Ansible’s three core components—ansible.cfg settings, inventory definitions (including INI, YAML, and dynamic scripts), and Playbook structure—showing practical examples, performance tweaks, variable hierarchies, conditionals, loops, handlers, tags, blocks, roles, and common pitfalls to help you manage hundreds of servers efficiently.

AI Agent Super App
AI Agent Super App
AI Agent Super App
Mastering Ansible: Complete Guide to ansible.cfg, Inventory, and Playbooks

1. ansible.cfg – the brain of Ansible

Ansible’s behavior is driven almost entirely by configuration files. It searches for configuration in the following order:

./ansible.cfg in the current directory

~/.ansible.cfg in the user’s home directory

/etc/ansible/ansible.cfg (global)

Placing an ansible.cfg in a project directory overrides the global file, allowing independent settings per project.

Commonly changed parameters are under the [defaults] section:

[defaults]
inventory=./hosts
remote_user=ansible
forks=20
timeout=30
host_key_checking=False
log_path=/var/log/ansible.log
retry_files_enabled=False
ansible_managed=This file is managed by Ansible.
roles_path=./roles
library=./library

Key tips:

Specify multiple inventory files with commas, e.g., inventory=./prod_hosts,./dev_hosts Set forks according to the number of managed hosts (10 for up to 10 hosts, 20‑50 for up to 100, 50‑100 for a few hundred).

Increase timeout when the network is unreliable.

Enable log_path in production and ensure the running user can write to the log directory.

Disable host_key_checking for non‑interactive runs, but keep it enabled in high‑security environments and pre‑populate ~/.ssh/known_hosts.

Turn off retry_files_enabled unless you need automatic retry files.

2. Inventory – the host roster

Inventory tells Ansible which machines to manage, how they are grouped, and what host‑specific variables they have.

2.1 INI format

[webservers]
192.168.1.10
192.168.1.11
web01.example.com

[dbservers]
192.168.1.20
192.168.1.21

[production:children]
webservers
dbservers

Group definitions ( [group]), nested groups ( [group:children]), and host variables can be added directly after a host line, e.g.:

[webservers]
web01 ansible_host=192.168.1.10 ansible_port=2222 ansible_user=deploy
web02 ansible_host=192.168.1.11

[dbservers]
db01 ansible_host=192.168.1.20 ansible_user=root

Common host variables: ansible_host – real IP or DNS name ansible_port – SSH port (default 22) ansible_user – SSH login user ansible_ssh_private_key_file – private key path ansible_python_interpreter – Python interpreter on the target

2.2 Group variables

Define variables for an entire group with [group:vars]:

[webservers:vars]
ansible_user=deploy
ansible_port=2222
http_port=80
app_env=production

Hosts inherit these values unless they override them individually.

2.3 Host range syntax

Use patterns to generate multiple hosts without writing each line:

[webservers]
web[01:10].example.com

[dbservers]
db[01:05:2].example.com   # expands to db01, db03, db05

2.4 YAML inventory

For complex inventories, YAML improves readability:

all:
  children:
    webservers:
      hosts:
        web01:
          ansible_host: 192.168.1.10
          ansible_port: 2222
        web02:
          ansible_host: 192.168.1.11
    dbservers:
      hosts:
        db01:
          ansible_host: 192.168.1.20
      vars:
        db_port: 3306
        ansible_user: deploy

2.5 Dynamic inventory

When hosts are created and destroyed automatically (e.g., cloud), a script that outputs JSON can be used. Example Python script:

#!/usr/bin/env python3
import json
instances = get_cloud_instances()
inventory = {
    "webservers": {
        "hosts": ["10.0.1.1", "10.0.1.2"],
        "vars": {"http_port": 80}
    },
    "dbservers": {"hosts": ["10.0.2.1"]},
    "_meta": {"hostvars": {
        "10.0.1.1": {"ansible_user": "deploy"},
        "10.0.1.2": {"ansible_user": "deploy"},
        "10.0.2.1": {"ansible_user": "root"}
    }}
}
print(json.dumps(inventory, indent=2))

Official scripts such as ec2.py (AWS) or alicloud.py (Alibaba Cloud) can be referenced directly:

$ ansible -i /path/to/ec2.py all --list-hosts
[defaults]
inventory=/path/to/ec2.py

From Ansible 2.10+, the preferred way is to use an inventory plugin, e.g. aws_ec2.yaml :

plugin: aws_ec2
regions:
  - us-east-1
  - us-west-2
filters:
  tag:Environment: production
keyed_groups:
  - key: tags.Role
    prefix: role

3. Playbook – the heart of automation

A Playbook is a YAML file that describes a series of plays. Each play defines target hosts, privilege escalation, variables, and a list of tasks.

3.1 Minimal Playbook

---
- name: Install and start Nginx
  hosts: webservers
  become: yes
  tasks:
    - name: Install nginx
      yum:
        name: nginx
        state: present
    - name: Start nginx
      service:
        name: nginx
        state: started
        enabled: yes

Key concepts:

Play – a set of tasks applied to a group of hosts.

Task – a single action, usually a module call.

Module – the executable unit (e.g., yum, service, copy).

3.2 Full play structure

A complete play includes vars , pre_tasks , roles , tasks , post_tasks , and handlers :

---
- name: Deploy Web Service
  hosts: webservers
  become: yes
  gather_facts: yes
  vars:
    http_port: 80
    server_name: example.com
    vars_files:
      - vars/common.yml
  pre_tasks:
    - name: Show start message
      debug:
        msg: "Starting deployment..."
  roles:
    - common
    - nginx
  tasks:
    - name: Deploy configuration
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

3.3 Variable precedence

Variables are resolved from lowest to highest priority:

Defined directly in the Playbook.

External variable files ( vars_files).

Inventory variables (group or host).

Command‑line extra vars (e.g., -e "app_port=9090").

3.4 Conditionals (when)

Run tasks only when a condition is true, for example:

- name: Install Apache on Debian
  apt:
    name: apache2
    state: present
  when: ansible_os_family == "Debian"

- name: Install Apache on RedHat
  yum:
    name: httpd
    state: present
  when: ansible_os_family == "RedHat"

3.5 Loops

Iterate over simple lists or dictionaries:

- name: Install packages
  yum:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - php{{ php_version }}
    - php{{ php_version }}-fpm
    - php{{ php_version }}-mysql
    - git
    - curl

3.6 Handlers

Handlers run only when a task reports changed and are triggered by notify :

- name: Deploy nginx config
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: restart nginx

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

Handlers execute at the end of the play; they run once even if notified multiple times.

3.7 Tags

Tag tasks to run a subset of the playbook:

- name: Install nginx
  yum:
    name: nginx
    state: present
  tags: [install, nginx]

Run with --tags nginx , skip with --skip-tags config , or list all tags with --list-tags .

3.8 Blocks

Group tasks and define error handling:

- name: Configure Web Service
  block:
    - name: Install nginx
      yum:
        name: nginx
        state: present
    - name: Start nginx
      service:
        name: nginx
        state: started
  rescue:
    - name: Report failure
      debug:
        msg: "nginx installation failed"
  always:
    - name: Clean temporary files
      file:
        path: /tmp/nginx_install_temp
        state: absent

3.9 Roles

Roles provide a reusable directory structure. A typical role layout includes tasks/main.yml , handlers/main.yml , templates/ , files/ , vars/main.yml , defaults/main.yml , and meta/main.yml . Use them in a playbook with the roles list.

3.10 Full example – deploying Nginx + PHP

---
- name: Deploy Nginx + PHP
  hosts: webservers
  become: yes
  gather_facts: yes
  vars:
    http_port: 80
    php_version: "8.2"
    app_user: www-data
  pre_tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"
  tasks:
    - name: Install packages
      package:
        name:
          - nginx
          - "php{{ php_version }}"
          - "php{{ php_version }}-fpm"
          - "php{{ php_version }}-mysql"
          - git
          - curl
        state: present
    - name: Create application directories
      file:
        path: "{{ item }}"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: "0755"
      loop:
        - /var/www/app
        - /var/www/app/public
        - /var/www/app/storage
    - name: Deploy code
      git:
        repo: "https://github.com/myorg/myapp.git"
        dest: /var/www/app
        version: main
        force: yes
      notify: reload php-fpm
    - name: Deploy nginx config
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/app
        owner: root
        group: root
        mode: "0644"
      notify: restart nginx
    - name: Enable site
      file:
        src: /etc/nginx/sites-available/app
        dest: /etc/nginx/sites-enabled/app
        state: link
      notify: restart nginx
    - name: Ensure nginx is running
      service:
        name: nginx
        state: started
        enabled: yes
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted
    - name: reload php-fpm
      service:
        name: "php{{ php_version }}-fpm"
        state: reloaded
  post_tasks:
    - name: Verify nginx
      uri:
        url: "http://localhost:{{ http_port }}"
        status_code: 200
      register: result
      retries: 3
      delay: 5
      until: result.status == 200

3.11 Debugging tricks

Useful command‑line options: --syntax-check – validate YAML syntax. --list-hosts – show which hosts will be targeted. --list-tasks – list all tasks. --check – run in dry‑run mode (not supported by shell / command). --step – pause before each task. -vvv – increase verbosity.

3.12 Common pitfalls

YAML indentation must use spaces, not tabs.

Every task should have a name for readability.

Maintain idempotence – running a playbook repeatedly should produce the same result.

Never store plain‑text secrets; use ansible-vault to encrypt them.

Prefix variable names to avoid collisions (e.g., nginx_port).

Handler names must be unique across the entire play.

Use the .j2 suffix for Jinja2 templates.

In summary, the article covers the three core Ansible configuration files, provides practical tips for ansible.cfg , demonstrates how to manage inventories of any size, and walks through building robust, maintainable Playbooks with variables, conditionals, loops, handlers, tags, blocks, and roles.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Inventoryansible.cfg
AI Agent Super App
Written by

AI Agent Super App

AI agent applications, installation, large-model testing, computer fundamentals, IT operations and maintenance exchange, network technology exchange, Linux learning

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.