Files
howtos/topics/ansible-roles.md
user 80f82dcde7 docs: add ansible howto collection
Four reference files covering:
- ansible.md — core commands, ansible.cfg, key settings
- ansible-inventory.md — static/dynamic inventory, directory layout
- ansible-variables.md — 22-level precedence, scoping, merge behavior
- ansible-roles.md — structure, defaults vs vars, dependencies
2026-02-21 20:33:34 +01:00

5.7 KiB

Ansible Roles

Reusable, self-contained units of automation with a standard directory structure.

Role Directory Structure

roles/
└── nginx/
    ├── defaults/
    │   └── main.yml        # Low-precedence defaults (user overrides these)
    ├── vars/
    │   └── main.yml        # High-precedence vars (internal to role)
    ├── tasks/
    │   └── main.yml        # Entry point for tasks
    ├── handlers/
    │   └── main.yml        # Handlers triggered by notify
    ├── templates/
    │   └── nginx.conf.j2   # Jinja2 templates
    ├── files/
    │   └── index.html      # Static files for copy module
    ├── meta/
    │   └── main.yml        # Dependencies, galaxy metadata
    └── README.md

All directories are optional — include only what the role needs.

What Goes Where

Directory Purpose Precedence
defaults/ Default variable values Lowest — meant to be overridden
vars/ Internal role variables High — not easily overridden
tasks/ Main task list Entry point: tasks/main.yml
handlers/ Restart/reload triggers Run once at end of play
templates/ Jinja2 files (.j2) Referenced as template: nginx.conf.j2
files/ Static files Referenced as copy: src=index.html
meta/ Role deps, platform info Processed before role tasks run

Creating a Role

# Scaffold with ansible-galaxy
ansible-galaxy role init roles/nginx

# Minimal manual structure
mkdir -p roles/nginx/{tasks,defaults,templates,handlers,meta}

Using Roles

In a Playbook

# site.yml
- hosts: webservers
  roles:
    - nginx                           # simple
    - role: nginx                     # with params
      vars:
        nginx_listen_port: 8080
    - role: nginx                     # conditional
      when: install_nginx | bool
    - role: nginx                     # tagged
      tags: [web, nginx]

With include_role / import_role

tasks:
  # Static import — processed at parse time
  - import_role:
      name: nginx

  # Dynamic include — processed at runtime (supports loops/conditionals)
  - include_role:
      name: "{{ item }}"
    loop:
      - nginx
      - certbot
import_role include_role
When parsed Playbook load Runtime
Tags/when Applied to all tasks inside Applied to include itself
Loops Not supported Supported
Handlers Visible globally Scoped to include

Role Dependencies

# roles/nginx/meta/main.yml
dependencies:
  - role: common
  - role: firewall
    vars:
      firewall_allowed_ports:
        - 80
        - 443

Dependencies run before the role's own tasks. Duplicate dependencies are skipped unless allow_duplicates: true is set.

Example Role: nginx

# roles/nginx/defaults/main.yml
nginx_listen_port: 80
nginx_worker_processes: auto
nginx_server_name: "_"
nginx_root: /var/www/html
nginx_access_log: /var/log/nginx/access.log
# roles/nginx/tasks/main.yml
- name: Install nginx
  apt:
    name: nginx
    state: present
    update_cache: true

- name: Deploy configuration
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: "0644"
  notify: Reload nginx

- name: Enable and start nginx
  service:
    name: nginx
    state: started
    enabled: true
# roles/nginx/handlers/main.yml
- name: Reload nginx
  service:
    name: nginx
    state: reloaded

Splitting Tasks

# roles/nginx/tasks/main.yml
- import_tasks: install.yml
- import_tasks: configure.yml
- import_tasks: service.yml

# Conditional platform tasks
- import_tasks: debian.yml
  when: ansible_os_family == "Debian"
- import_tasks: redhat.yml
  when: ansible_os_family == "RedHat"

Project Layout with Roles

ansible-project/
├── ansible.cfg
├── site.yml                    # Master playbook
├── webservers.yml              # Play for web tier
├── dbservers.yml               # Play for db tier
├── inventory/
│   ├── hosts.yml
│   ├── group_vars/
│   └── host_vars/
├── roles/
│   ├── common/                 # Shared baseline
│   ├── nginx/
│   ├── postgresql/
│   └── requirements.yml        # Galaxy dependencies
└── collections/
    └── requirements.yml        # Collection dependencies
# roles/requirements.yml
- src: geerlingguy.docker
  version: "6.1.0"
- src: geerlingguy.certbot
ansible-galaxy install -r roles/requirements.yml -p roles/

Gotchas

  • defaults/ is for users to override; vars/ is for role internals — mixing them up breaks precedence
  • Handlers run at end of play, not after each task — use meta: flush_handlers if needed mid-play
  • Role names must be valid Python identifiers (no hyphens) when used as variable namespaces
  • import_role makes role tasks visible to --list-tasks; include_role does not
  • Dependencies declared in meta/ run every time the role is referenced unless deduplicated
  • Template paths are relative to templates/ — use nginx.conf.j2, not templates/nginx.conf.j2
  • File paths in copy are relative to files/ — use index.html, not files/index.html

See Also

  • ansible — core commands and ansible.cfg
  • ansible-variables — how defaults/ vs vars/ fits into precedence
  • ansible-inventory — inventory structure for role-consuming projects