Ansible Roles - Organizing Your Automation

Status: Active
Last Updated: 2026-01-30
Category: Infrastructure - Configuration Management
Prerequisites: ansible-playbooks, ansible-inventory
Time: 3-4 hours
Tags: ansible, roles, organization, reusability, galaxy

Summary

Master Ansible roles to create reusable, maintainable automation code. Learn role structure, dependencies, variables, defaults, and how to leverage Ansible Galaxy for community roles. Build production-ready role-based infrastructure automation.

๐ŸŽฏ What You'll Learn

By the end of this article, you'll be able to:

๐Ÿค” Why Roles?

Without Roles (Messy!)

# site.yaml - 1000+ lines, hard to maintain
- name: Configure everything
  hosts: webservers
  tasks:
    - name: Install Nginx
      apt: ...
    - name: Configure Nginx
      template: ...
    - name: Install PostgreSQL
      apt: ...
    - name: Configure PostgreSQL
      template: ...
    # ... 100 more tasks

With Roles (Clean!)

# site.yaml - clean and organized
- name: Configure web servers
  hosts: webservers
  roles:
    - common
    - nginx
    - postgresql
    - monitoring

Benefits:


๐Ÿ“ Role Structure

Standard Role Directory

roles/
โ””โ”€โ”€ nginx/
    โ”œโ”€โ”€ tasks/
    โ”‚   โ””โ”€โ”€ main.yaml          # Main tasks
    โ”œโ”€โ”€ handlers/
    โ”‚   โ””โ”€โ”€ main.yaml          # Handlers (service restarts, etc.)
    โ”œโ”€โ”€ templates/
    โ”‚   โ””โ”€โ”€ nginx.conf.j2      # Jinja2 templates
    โ”œโ”€โ”€ files/
    โ”‚   โ””โ”€โ”€ index.html         # Static files
    โ”œโ”€โ”€ vars/
    โ”‚   โ””โ”€โ”€ main.yaml          # Variables (high priority)
    โ”œโ”€โ”€ defaults/
    โ”‚   โ””โ”€โ”€ main.yaml          # Default variables (low priority)
    โ”œโ”€โ”€ meta/
    โ”‚   โ””โ”€โ”€ main.yaml          # Role metadata and dependencies
    โ”œโ”€โ”€ tests/
    โ”‚   โ”œโ”€โ”€ inventory          # Test inventory
    โ”‚   โ””โ”€โ”€ test.yaml          # Test playbook
    โ””โ”€โ”€ README.md              # Documentation

Only create directories you need! Empty directories are fine to skip.


๐Ÿ—๏ธ Creating Your First Role

Generate Role Skeleton

# Create role structure
ansible-galaxy init roles/nginx

# Or in specific directory
ansible-galaxy init --init-path roles nginx

Generated structure:

roles/nginx/
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ defaults/
โ”‚   โ””โ”€โ”€ main.yml
โ”œโ”€โ”€ files/
โ”œโ”€โ”€ handlers/
โ”‚   โ””โ”€โ”€ main.yml
โ”œโ”€โ”€ meta/
โ”‚   โ””โ”€โ”€ main.yml
โ”œโ”€โ”€ tasks/
โ”‚   โ””โ”€โ”€ main.yml
โ”œโ”€โ”€ templates/
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ inventory
โ”‚   โ””โ”€โ”€ test.yml
โ””โ”€โ”€ vars/
    โ””โ”€โ”€ main.yml

Build Nginx Role

roles/nginx/defaults/main.yaml:

---
# Default variables (can be overridden)
nginx_port: 80
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65

nginx_sites:
  - name: default
    template: default.conf.j2
    enabled: true

roles/nginx/vars/main.yaml:

---
# High-priority variables (harder to override)
nginx_user: www-data
nginx_config_dir: /etc/nginx
nginx_log_dir: /var/log/nginx

roles/nginx/tasks/main.yaml:

---
- name: Install Nginx
  apt:
    name: nginx
    state: present
    update_cache: yes

- name: Create config directory
  file:
    path: "{{ nginx_config_dir }}/sites-available"
    state: directory
    mode: '0755'

- name: Configure Nginx main config
  template:
    src: nginx.conf.j2
    dest: "{{ nginx_config_dir }}/nginx.conf"
    validate: 'nginx -t -c %s'
  notify: Restart Nginx

- name: Configure sites
  template:
    src: "{{ item.template }}"
    dest: "{{ nginx_config_dir }}/sites-available/{{ item.name }}"
  loop: "{{ nginx_sites }}"
  when: item.enabled | default(true)
  notify: Reload Nginx

- name: Enable sites
  file:
    src: "{{ nginx_config_dir }}/sites-available/{{ item.name }}"
    dest: "{{ nginx_config_dir }}/sites-enabled/{{ item.name }}"
    state: link
  loop: "{{ nginx_sites }}"
  when: item.enabled | default(true)
  notify: Reload Nginx

- name: Start and enable Nginx
  service:
    name: nginx
    state: started
    enabled: yes

roles/nginx/handlers/main.yaml:

---
- name: Restart Nginx
  service:
    name: nginx
    state: restarted

- name: Reload Nginx
  service:
    name: nginx
    state: reloaded

- name: Test Nginx config
  command: nginx -t
  changed_when: false

roles/nginx/templates/nginx.conf.j2:

user {{ nginx_user }};
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;

events {
    worker_connections {{ nginx_worker_connections }};
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout {{ nginx_keepalive_timeout }};
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log {{ nginx_log_dir }}/access.log;
    error_log {{ nginx_log_dir }}/error.log;

    gzip on;

    include /etc/nginx/sites-enabled/*;
}

roles/nginx/templates/default.conf.j2:

server {
    listen {{ nginx_port }};
    server_name _;

    root /var/www/html;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}

roles/nginx/meta/main.yaml:

---
galaxy_info:
  author: Your Name
  description: Nginx web server role
  company: Your Company
  license: MIT
  min_ansible_version: '2.9'
  
  platforms:
    - name: Ubuntu
      versions:
        - focal
        - jammy
    - name: Debian
      versions:
        - bullseye

  galaxy_tags:
    - nginx
    - web
    - webserver

dependencies: []

Use the Role

playbook.yaml:

---
- name: Configure web servers
  hosts: webservers
  become: yes
  
  roles:
    - nginx

With variable overrides:

---
- name: Configure web servers
  hosts: webservers
  become: yes
  
  roles:
    - role: nginx
      vars:
        nginx_port: 8080
        nginx_sites:
          - name: mysite
            template: mysite.conf.j2
            enabled: true

๐Ÿ”— Role Dependencies

Define Dependencies

roles/webapp/meta/main.yaml:

---
dependencies:
  - role: common
  - role: nginx
    vars:
      nginx_port: 80
  - role: postgresql
    vars:
      postgres_version: 15

When webapp role runs, it automatically includes common, nginx, and postgresql roles first.


Conditional Dependencies

dependencies:
  - role: ssl
    when: ssl_enabled | bool
  
  - role: monitoring
    when: env == "production"

๐Ÿ“ฆ Multiple Roles Example

Project structure:

ansible-project/
โ”œโ”€โ”€ ansible.cfg
โ”œโ”€โ”€ inventory/
โ”‚   โ””โ”€โ”€ hosts.yaml
โ”œโ”€โ”€ playbooks/
โ”‚   โ”œโ”€โ”€ site.yaml
โ”‚   โ”œโ”€โ”€ webservers.yaml
โ”‚   โ””โ”€โ”€ databases.yaml
โ””โ”€โ”€ roles/
    โ”œโ”€โ”€ common/
    โ”œโ”€โ”€ nginx/
    โ”œโ”€โ”€ postgresql/
    โ”œโ”€โ”€ monitoring/
    โ””โ”€โ”€ security/

Common Role

roles/common/tasks/main.yaml:

---
- name: Update apt cache
  apt:
    update_cache: yes
    cache_valid_time: 3600

- name: Install common packages
  apt:
    name:
      - curl
      - wget
      - vim
      - git
      - htop
      - tmux
    state: present

- name: Configure timezone
  timezone:
    name: "{{ timezone | default('UTC') }}"

- name: Configure NTP
  apt:
    name: systemd-timesyncd
    state: present

- name: Start timesyncd
  service:
    name: systemd-timesyncd
    state: started
    enabled: yes

- name: Create admin users
  user:
    name: "{{ item.name }}"
    groups: "{{ item.groups | default([]) }}"
    shell: /bin/bash
    state: present
  loop: "{{ admin_users | default([]) }}"

- name: Add SSH keys for admins
  authorized_key:
    user: "{{ item.name }}"
    key: "{{ item.ssh_key }}"
    state: present
  loop: "{{ admin_users | default([]) }}"
  when: item.ssh_key is defined

Security Role

roles/security/tasks/main.yaml:

---
- name: Install fail2ban
  apt:
    name: fail2ban
    state: present

- name: Configure fail2ban
  template:
    src: jail.local.j2
    dest: /etc/fail2ban/jail.local
  notify: Restart fail2ban

- name: Configure UFW defaults
  ufw:
    direction: "{{ item.direction }}"
    policy: "{{ item.policy }}"
  loop:
    - { direction: 'incoming', policy: 'deny' }
    - { direction: 'outgoing', policy: 'allow' }

- name: Allow SSH
  ufw:
    rule: allow
    port: "{{ ssh_port | default('22') }}"
    proto: tcp

- name: Allow HTTP/HTTPS
  ufw:
    rule: allow
    port: "{{ item }}"
    proto: tcp
  loop:
    - '80'
    - '443'
  when: allow_web | default(false)

- name: Enable UFW
  ufw:
    state: enabled

- name: Disable root SSH login
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?PermitRootLogin'
    line: 'PermitRootLogin no'
  notify: Restart SSH

- name: Disable password authentication
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?PasswordAuthentication'
    line: 'PasswordAuthentication no'
  notify: Restart SSH
  when: disable_password_auth | default(false)

Site Playbook

playbooks/site.yaml:

---
- name: Configure all servers
  hosts: all
  become: yes
  
  roles:
    - common
    - security

- name: Configure web servers
  hosts: webservers
  become: yes
  
  roles:
    - nginx
    - monitoring

- name: Configure database servers
  hosts: databases
  become: yes
  
  roles:
    - postgresql
    - monitoring

๐ŸŒ Ansible Galaxy

Ansible Galaxy: Public repository of community roles.

Search Galaxy

# Search for roles
ansible-galaxy search nginx

# Search with filters
ansible-galaxy search nginx --platforms Ubuntu

# Search by author
ansible-galaxy search --author geerlingguy

Install Role from Galaxy

# Install role
ansible-galaxy install geerlingguy.nginx

# Install specific version
ansible-galaxy install geerlingguy.nginx,2.8.0

# Install to specific path
ansible-galaxy install geerlingguy.nginx -p ./roles

# Install from requirements file
ansible-galaxy install -r requirements.yaml

Requirements File

requirements.yaml:

---
# From Galaxy
- name: geerlingguy.nginx
  version: 2.8.0

- name: geerlingguy.postgresql
  version: 3.4.0

# From Git repository
- src: https://github.com/company/ansible-role-custom
  version: main
  name: custom

# From GitHub
- src: git+https://github.com/company/ansible-role-app.git
  version: v1.0.0
  name: app

Install all:

ansible-galaxy install -r requirements.yaml

Use Galaxy Role

---
- name: Configure servers
  hosts: webservers
  become: yes
  
  roles:
    - geerlingguy.nginx
    - geerlingguy.postgresql

Publish Your Role

1. Create role on Galaxy (GitHub integration)

2. Push to GitHub:

cd roles/my-role
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/user/ansible-role-my-role.git
git push -u origin main

3. Import to Galaxy:


๐ŸŽฏ Advanced Role Patterns

Role with Multiple Task Files

roles/app/tasks/main.yaml:

---
- import_tasks: install.yaml
- import_tasks: configure.yaml
- import_tasks: deploy.yaml
- import_tasks: monitoring.yaml

roles/app/tasks/install.yaml:

---
- name: Install dependencies
  apt:
    name:
      - python3
      - python3-pip
    state: present

# ... more install tasks

Conditional Task Inclusion

---
- name: Include OS-specific tasks
  include_tasks: "{{ ansible_os_family }}.yaml"

- name: Include environment-specific tasks
  include_tasks: "{{ env }}.yaml"
  when: env is defined

Role Variables Priority

Priority order (highest to lowest):

  1. Extra vars (-e on command line)
  2. Task vars
  3. Block vars
  4. Role vars (roles/x/vars/main.yaml)
  5. Play vars
  6. Host facts
  7. Host vars
  8. Group vars
  9. Role defaults (roles/x/defaults/main.yaml)

๐Ÿงช Testing Roles

Molecule

Install:

pip install molecule molecule-docker

Initialize:

cd roles/nginx
molecule init scenario

Test:

# Create test instance
molecule create

# Run converge (apply role)
molecule converge

# Run tests
molecule verify

# Destroy instance
molecule destroy

# Full test cycle
molecule test

Simple Test Playbook

roles/nginx/tests/test.yaml:

---
- name: Test nginx role
  hosts: localhost
  remote_user: root
  
  roles:
    - nginx
  
  post_tasks:
    - name: Check if Nginx is running
      service:
        name: nginx
        state: started
      check_mode: yes
      register: result
      failed_when: result.changed

    - name: Check if Nginx responds
      uri:
        url: http://localhost
        status_code: 200

๐Ÿ’ก Best Practices

1. Use Defaults

# roles/app/defaults/main.yaml
---
app_port: 8080
app_workers: 4
app_log_level: info

Users can override easily:

roles:
  - role: app
    vars:
      app_port: 9000

2. Document Your Role

README.md:

# Nginx Role

Installs and configures Nginx web server.

## Requirements

- Ansible 2.9+
- Ubuntu 20.04+

## Role Variables

- `nginx_port`: Port to listen on (default: 80)
- `nginx_worker_processes`: Number of worker processes (default: auto)

## Dependencies

None

## Example Playbook

\`\`\`yaml
- hosts: webservers
  roles:
    - role: nginx
      nginx_port: 8080
\`\`\`

## License

MIT

3. Keep Roles Focused

# Good: Focused roles
roles/
โ”œโ”€โ”€ nginx/
โ”œโ”€โ”€ postgresql/
โ””โ”€โ”€ redis/

# Bad: God role
roles/
โ””โ”€โ”€ everything/  # Does too much!

4. Use Meta Dependencies

# roles/webapp/meta/main.yaml
dependencies:
  - common  # Always needed
  - nginx   # Web server

5. Version Your Roles

Git tags:

git tag v1.0.0
git push --tags

Semantic versioning: major.minor.patch


๐Ÿ”— What's Next?

Now that you can create and use roles:

Security:

Advanced Patterns:

Testing:


๐Ÿ“š Resources

Official Docs:

Popular Roles:

Testing:


๐Ÿ“ Change Log

2026-01-30


Next Article: ansible-vault - Secure your secrets!

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse