Skip to content

Ansible Roles — Reusable Automation Components Guide

DodaTech Updated 2026-06-24 6 min read

In this tutorial, you'll learn about Ansible Roles. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Ansible roles are self-contained units of automation that package tasks, handlers, variables, templates, and files into reusable components with a standardized directory layout.

What You'll Learn

Why It Matters

Flat playbook structures become unmanageable beyond 50 tasks. Roles organize automation into logical components — each role encapsulates everything needed to configure a specific service or capability. DodaTech maintains 30+ Ansible roles for common infrastructure components (nginx, PostgreSQL, Prometheus, filebeat, node_exporter, docker, security-hardening) shared across all teams.

Real-World Use

DodaZIP's infrastructure team develops Ansible roles in a dedicated <a href="/devops/ansible/">Ansible</a>-roles Repository. Each role is independently versioned with Git tags, tested with Molecule, and published to an internal Ansible Galaxy server. Teams consume roles via requirements.yml, pinning specific versions for stability.

flowchart TD
    A[Ansible Role: nginx] --> B[defaults/main.yml]
    A --> C[tasks/main.yml]
    A --> D[handlers/main.yml]
    A --> E[templates/]
    A --> F[vars/]
    A --> G[meta/main.yml]
    B --> H[Default values]
    C --> I[Install, configure, deploy tasks]
    D --> J[reload nginx handler]
    E --> K[nginx.conf.j2, vhost.conf.j2]
    F --> L[OS-specific overrides]
    G --> M[Dependencies & galaxy info]
    style A fill:#EE0000,color:#fff
â„šī¸ Info

Prerequisites: Understanding of Ansible playbooks, YAML syntax, and basic Linux administration.

Role Directory Structure

ansible-roles/
  nginx/
    defaults/
      main.yml         # Lowest priority variables
    vars/
      main.yml         # OS-specific variable overrides
    tasks/
      main.yml         # Main task list
      install.yml      # Included tasks for installation
      configure.yml    # Included tasks for configuration
      security.yml     # Included tasks for hardening
    handlers/
      main.yml         # Service handlers
    templates/
      nginx.conf.j2    # Main config template
      vhost.conf.j2    # Virtual host template
      ssl-params.conf.j2
    files/
      dhparam.pem      # Static files for deployment
    meta/
      main.yml         # Role metadata & dependencies
    molecule/
      default/
        converge.yml   # Test scenario

Creating a Role

# nginx/defaults/main.yml
---
nginx_version: "1.26"
nginx_user: nginx
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_server_tokens: off
nginx_remove_default_vhost: true
nginx_vhosts:
  - server_name: "api.dodatech.com"
    root: "/opt/dodatech/current/public"
    proxy_pass: "http://localhost:3000"
    ssl: true
    ssl_certificate: "/etc/ssl/certs/dodatech.crt"
    ssl_certificate_key: "/etc/ssl/private/dodatech.key"
nginx_vhosts_extra: {}
nginx_log_format: main
nginx_access_log: /var/log/nginx/access.log
nginx_error_log: /var/log/nginx/error.log
# nginx/vars/main.yml
---
# OS-specific package names
nginx_package: nginx
nginx_service: nginx
nginx_config_path: /etc/nginx/nginx.conf
nginx_confd_path: /etc/nginx/conf.d
nginx_sites_enabled_path: /etc/nginx/sites-enabled
# nginx/tasks/main.yml
---
- name: Include OS-specific variables
  include_vars: "{{ ansible_os_family }}.yml"
  when: ansible_os_family != "Debian"

- name: Install NGINX
  include_tasks: install.yml

- name: Configure NGINX
  include_tasks: configure.yml

- name: Deploy virtual hosts
  include_tasks: vhosts.yml

- name: Apply security hardening
  include_tasks: security.yml
  when: nginx_harden | default(true) | bool
# nginx/tasks/install.yml
---
- name: Add NGINX repository key
  apt_key:
    url: "https://nginx.org/keys/nginx_signing.key"
    state: present
  when: ansible_os_family == "Debian"

- name: Add NGINX repository
  apt_repository:
    repo: "deb https://nginx.org/packages/{{ ansible_distribution | lower }}/ {{ ansible_distribution_release }} nginx"
    state: present
  when: ansible_os_family == "Debian"

- name: Install NGINX package
  package:
    name: "nginx={{ nginx_version }}*"
    state: present
  notify: restart nginx
# nginx/tasks/configure.yml
---
- name: Create configuration directories
  file:
    path: "{{ item }}"
    state: directory
    owner: root
    group: root
    mode: 0755
  loop:
    - "{{ nginx_confd_path }}"
    - /etc/nginx/ssl
    - /var/log/nginx

- name: Deploy main NGINX configuration
  template:
    src: nginx.conf.j2
    dest: "{{ nginx_config_path }}"
    owner: root
    group: root
    mode: 0644
  notify: reload nginx

- name: Remove default virtual host
  file:
    path: "{{ nginx_sites_enabled_path }}/default"
    state: absent
  when: nginx_remove_default_vhost
  notify: reload nginx
# nginx/tasks/vhosts.yml
---
- name: Deploy virtual host configurations
  template:
    src: vhost.conf.j2
    dest: "{{ nginx_confd_path }}/{{ item.server_name }}.conf"
    owner: root
    group: root
    mode: 0644
  loop: "{{ nginx_vhosts }}"
  notify: reload nginx
# nginx/handlers/main.yml
---
- name: restart nginx
  service:
    name: "{{ nginx_service }}"
    state: restarted
  listen: "restart nginx"

- name: reload nginx
  service:
    name: "{{ nginx_service }}"
    state: reloaded
  listen: "reload nginx"
# nginx/meta/main.yml
---
galaxy_info:
  author: DodaTech DevOps
  description: NGINX web server and reverse proxy
  company: DodaTech
  license: MIT
  min_ansible_version: "2.16"
  platforms:
    - name: Ubuntu
      versions:
        - "22.04"
        - "24.04"
    - name: Debian
      versions:
        - "11"
        - "12"
  galaxy_tags:
    - nginx
    - webserver
    - proxy
    - reverse-proxy

dependencies:
  - role: common
    vars:
      common_monitoring: true

Using Roles in a Playbook

# site.yml
- name: Configure web servers
  hosts: web
  become: yes
  vars:
    nginx_vhosts:
      - server_name: "www.dodatech.com"
        root: "/opt/dodatech/frontend"
        ssl: true
    app_version: "2.5.0"
    node_version: "22"
  roles:
    - role: common
      vars:
        common_ntp_enabled: true
        common_firewall_enabled: true
    - role: nginx
    - role: nodejs
    - role: app-deploy

- name: Configure database servers
  hosts: database
  become: yes
  vars:
    postgres_version: "16"
    postgres_max_connections: 200
  roles:
    - role: common
    - role: postgresql

Role Dependencies

# postgresql/meta/main.yml
dependencies:
  - role: common
  - role: apt-repository
    vars:
      repository_key: "https://www.postgresql.org/media/keys/ACCC4CF8.asc"
      repository_url: "deb http://apt.postgresql.org/pub/repos/apt {{ ansible_distribution_release }}-pgdg main"
  - role: monitoring-agent
    vars:
      exporter_port: 9187

Testing Roles with Molecule

# nginx/molecule/default/converge.yml
---
- name: Converge
  hosts: all
  become: yes
  vars:
    nginx_vhosts:
      - server_name: "test.dodatech.com"
        root: "/var/www/test"
  roles:
    - role: nginx
# Install molecule
pip install molecule molecule-plugins[docker]

# Test the role
molecule test

# Expected output:
# --> Scenario: default
# --> Action: dependency
# --> Action: lint
# --> Action: syntax
# --> Action: create
# --> Action: prepare
# --> Action: converge
# --> Action: idempotence
# --> Action: side_effect
# --> Action: verify
# --> Action: cleanup
# --> Action: destroy

Galaxy Requirements

# requirements.yml
---
roles:
  - name: dodatech.nginx
    src: https://gitlab.com/dodatech/ansible-roles/nginx.git
    version: v2.1.0
    scm: git

  - name: dodatech.postgresql
    src: https://gitlab.com/dodatech/ansible-roles/postgresql.git
    version: v3.0.0
    scm: git

  - name: geerlingguy.firewall
    src: geerlingguy.firewall
    version: 3.0.0
# Install roles from requirements
ansible-galaxy role install -r requirements.yml -p roles/

# Expected output:
# - extracting dodatech.nginx to roles/dodatech.nginx
# - dodatech.nginx was installed successfully
# - extracting dodatech.postgresql to roles/dodatech.postgresql
# - dodatech.postgresql was installed successfully
# - extracting geerlingguy.firewall to roles/geerlingguy.firewall

Common Configuration Mistakes

  1. Not using defaults/main.yml for configurable values: Hardcoding values in tasks/main.yml prevents user overrides. All configurable values belong in defaults/main.yml.

  2. Putting sensitive data in role defaults: Default variables are visible in version control and Galaxy metadata. Use Ansible Vault or environment-specific group_vars for secrets.

  3. Missing OS-specific variable files: Roles that only work on one OS break when used on different distributions. Use vars/{{ <a href="/devops/ansible/">Ansible</a>_os_family }}.yml and vars/{{ <a href="/devops/ansible/">Ansible</a>_distribution }}.yml.

  4. Creating monolithic task files: A single tasks/main.yml with 200 tasks is unmaintainable. Break tasks into logical includes (install.yml, configure.yml, security.yml).

  5. Not testing roles with Molecule: Untested roles break silently. Molecule validates idempotence, syntax, and convergence in isolated Docker or VM environments.

Practice Questions

  1. What is the purpose of the defaults/ directory in a role? Answer: defaults/main.yml defines the lowest-priority variables that users can easily override. All configurable role behavior should use defaults.

  2. How do role dependencies work? Answer: Role dependencies are defined in meta/main.yml. When a playbook includes a role, all its dependencies run first in the specified order.

  3. What is the difference between vars/ and defaults/? Answer: Variables in vars/ have higher priority than defaults/ and cannot be overridden by playbook variables. Use vars/ for OS-specific values you don't want changed.

  4. How does Molecule test roles? Answer: Molecule creates disposable test instances (Docker/VirtualBox/cloud), runs the role, verifies idempotence, and executes verification tests.

Challenge

Create an Ansible role for a complete monitoring stack: Prometheus server, Node Exporter, and Grafana. Include OS-specific package installation, template-based configuration files, firewall rule management, service handlers, and Molecule test scenarios for both Ubuntu 24.04 and Debian 12. Publish the role to Ansible Galaxy with proper metadata and documentation.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro