Ansible Roles and Galaxy Best Practices
Ansible roles provide a standardized way to organize and share automation code. They encapsulate tasks, handlers, variables, and templates into reusable components that can be applied across your infrastructure. This guide covers role structure, leveraging Ansible Galaxy for community roles, managing collections, handling dependencies, testing with Molecule, and implementing best practices for production-grade automation.
Table of Contents
- Understanding Ansible Roles
- Role Directory Structure
- Working with Ansible Galaxy
- Collections and Namespacing
- Managing Role Dependencies
- Testing Roles with Molecule
- Best Practices and Patterns
- Conclusion
Understanding Ansible Roles
Roles are the primary mechanism for organizing and sharing Ansible automation. A well-designed role is self-contained, idempotent, and testable. Roles allow you to break down complex playbooks into logical, reusable components that can be versioned, shared, and integrated into larger automation workflows.
The key benefits of using roles include improved code organization, easier maintenance, better code reuse, simplified testing, and seamless sharing through Ansible Galaxy. Instead of monolithic playbooks with dozens of tasks, roles let you structure automation around specific functionality.
Role Directory Structure
A properly structured Ansible role follows a standardized directory layout. When you create a new role using ansible-galaxy role init, this structure is automatically generated:
my_role/
├── defaults/
│ └── main.yml
├── files/
├── handlers/
│ └── main.yml
├── meta/
│ ├── main.yml
│ └── runtime.yml
├── README.md
├── tasks/
│ └── main.yml
├── templates/
├── tests/
│ ├── inventory
│ └── test.yml
└── vars/
└── main.yml
Each directory serves a specific purpose:
defaults/main.yml: Contains default variables with the lowest precedence. These can be easily overridden by users of the role. Always include sensible defaults here.
# defaults/main.yml
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_gzip_enabled: true
tasks/main.yml: The main task list executed when the role is applied. Break complex tasks into logical blocks with clear descriptions.
# tasks/main.yml
---
- name: Include OS-specific variables
include_vars:
file: "{{ ansible_os_family }}.yml"
- name: Install Nginx
package:
name: nginx
state: present
notify: restart nginx
- name: Create Nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
backup: yes
notify: restart nginx
handlers/main.yml: Contains handler tasks triggered by notify directives. Handlers run once at the end of plays, ideal for service restarts.
# handlers/main.yml
---
- name: restart nginx
systemd:
name: nginx
state: restarted
enabled: yes
vars/main.yml: Role-level variables with higher precedence than defaults. Use for role-specific values.
meta/main.yml: Metadata about the role including dependencies, author info, and Galaxy metadata.
# meta/main.yml
---
galaxy_info:
author: DevOps Team
description: Install and configure Nginx web server
company: Your Company
license: MIT
min_ansible_version: 2.9
platforms:
- name: Ubuntu
versions:
- focal
- jammy
- name: CentOS
versions:
- '8'
- '9'
categories:
- web
- system
dependencies: []
templates/: Jinja2 template files for configuration files. Templates allow dynamic content based on variables.
files/: Static files copied without modification to target systems.
tests/: Integration test playbook and inventory for basic testing before Galaxy submission.
Working with Ansible Galaxy
Ansible Galaxy is a hub for sharing Ansible roles and collections. It provides a centralized repository where you can discover, install, and publish automation code.
Installing roles from Galaxy is straightforward:
# Install a specific role
ansible-galaxy role install geerlingguy.docker
# Install multiple roles from a requirements file
ansible-galaxy role install -r requirements.yml
# Install a specific version
ansible-galaxy role install geerlingguy.docker,4.5.1
# Install to a custom path
ansible-galaxy role install -p ./roles geerlingguy.nodejs
Create a requirements.yml file to manage role dependencies across your projects:
# requirements.yml
---
roles:
- name: geerlingguy.docker
version: 4.5.1
src: https://github.com/geerlingguy/ansible-role-docker
- name: community.general
version: ">=3.0.0"
collections:
- name: community.docker
version: ">=3.0.0"
- name: ansible.posix
version: 1.5.1
Install all requirements with:
ansible-galaxy install -r requirements.yml
To publish your role to Galaxy, you need a Galaxy namespace and GitHub account. The process involves:
# Create a properly structured role directory
ansible-galaxy role init my-role
# Tag and push to GitHub
cd my-role
git tag 1.0.0
git push origin --tags
# Import into Galaxy (via web interface)
# Then make the repository public and import it
Collections and Namespacing
Collections represent the next evolution in Ansible content organization. Collections are structured as namespace.collection and can contain roles, plugins, modules, and documentation.
Collections provide:
- Namespace isolation preventing naming conflicts
- Unified versioning and distribution
- Better organization of related content
- Support for multiple content types beyond roles
Install collections from Galaxy:
ansible-galaxy collection install community.docker
ansible-galaxy collection install ansible.posix
ansible-galaxy collection install community.general
# Install from requirements file
ansible-galaxy collection install -r requirements.yml
Create a collection structure:
ansible-galaxy collection init my_namespace.my_collection
This creates:
my_namespace/
└── my_collection/
├── galaxy.yml
├── plugins/
├── roles/
├── modules/
├── README.md
└── docs/
Use collections in playbooks:
---
- hosts: all
tasks:
- name: Ensure Docker container is running
community.docker.docker_container:
name: myapp
image: myapp:latest
state: started
ports:
- "8080:8080"
Managing Role Dependencies
Roles often depend on other roles. Manage these dependencies in the role's meta/main.yml:
# meta/main.yml
---
dependencies:
- name: geerlingguy.python
version: 2.1.0
- name: geerlingguy.nodejs
version: 6.5.1
when: install_nodejs
Dependencies are automatically installed when you install the parent role:
ansible-galaxy role install -r requirements.yml
For more control, manage dependencies explicitly in your requirements file:
---
roles:
- name: geerlingguy.python
version: 2.1.0
- name: geerlingguy.nodejs
version: 6.5.1
- name: geerlingguy.docker
version: 4.5.1
dependencies:
- geerlingguy.python
Avoid circular dependencies by structuring roles hierarchically. Keep dependencies minimal and document them clearly.
Testing Roles with Molecule
Molecule is the standard framework for testing Ansible roles. It automates the process of testing role logic against multiple operating systems and configurations.
Install Molecule:
pip install molecule molecule-docker molecule-podman
Initialize Molecule for your role:
cd my_role
molecule init scenario -d docker
This creates a molecule/ directory:
molecule/
├── default/
│ ├── converge.yml
│ ├── molecule.yml
│ ├── prepare.yml
│ ├── verify.yml
│ └── side_effect.yml
└── ...
Configure molecule/default/molecule.yml to define test scenarios:
---
driver:
name: docker
provisioner:
name: ansible
verifier:
name: ansible
platforms:
- name: ubuntu-20.04
image: geerlingguy/docker-ubuntu2004-ansible
pre_build_image: true
docker_host: unix:///var/run/docker.sock
- name: ubuntu-22.04
image: geerlingguy/docker-ubuntu2204-ansible
pre_build_image: true
- name: centos-8
image: geerlingguy/docker-centos8-ansible
pre_build_image: true
scenario:
name: default
test_sequence:
- lint
- destroy
- syntax
- create
- prepare
- converge
- idempotence
- verify
- destroy
The converge.yml playbook applies your role:
---
- hosts: all
gather_facts: true
vars:
nginx_worker_processes: 4
roles:
- role: nginx
The verify.yml playbook tests the role's results:
---
- hosts: all
gather_facts: false
tasks:
- name: Check Nginx is installed
package_facts:
manager: auto
- name: Verify Nginx package
assert:
that:
- "'nginx' in ansible_facts.packages"
fail_msg: Nginx package not found
- name: Check Nginx service is running
systemd:
name: nginx
register: nginx_service
- name: Verify Nginx service status
assert:
that:
- nginx_service.status.ActiveState == 'active'
Run Molecule tests:
# Run complete test sequence
molecule test
# Run specific steps
molecule lint
molecule create
molecule converge
molecule verify
molecule destroy
# Run idempotence check (apply role twice)
molecule idempotence
# Debug a specific platform
molecule converge -s default
molecule login -s default
Add linting to catch style issues:
pip install ansible-lint
molecule lint
Configure .ansible-lint:
---
skip_list:
- 'role-name'
- 'name[casing]'
Best Practices and Patterns
Naming Conventions: Use clear, descriptive role names that indicate purpose. Prefix your Galaxy roles with your namespace to avoid conflicts.
# Good
company_name.webserver
company_name.database
company_name.monitoring
# Avoid
role1
mysite
app
Variable Organization: Use consistent variable naming. Prefix role variables with the role name to avoid collisions:
# defaults/main.yml
nginx_port: 80
nginx_user: www-data
nginx_group: www-data
nginx_enable_ssl: false
Include Pattern: Use include_tasks or import_tasks to organize complex roles:
# tasks/main.yml
---
- name: Include OS-specific tasks
include_tasks: "{{ ansible_os_family }}.yml"
- name: Include configuration tasks
include_tasks: configure.yml
- name: Include service tasks
include_tasks: service.yml
Conditional Logic: Use when and conditionals to make roles flexible:
- name: Configure Nginx for SSL
block:
- name: Create SSL directory
file:
path: /etc/nginx/ssl
state: directory
mode: '0700'
- name: Install SSL certificate
copy:
src: "files/{{ inventory_hostname }}.crt"
dest: /etc/nginx/ssl/
notify: restart nginx
when: nginx_enable_ssl
Idempotence: Always write tasks that produce the same result when run multiple times:
# Good - idempotent
- name: Ensure Nginx is installed and running
package:
name: nginx
state: present
- name: Ensure Nginx service is enabled and running
systemd:
name: nginx
state: started
enabled: yes
# Bad - not idempotent
- name: Install Nginx
shell: apt-get install -y nginx
Error Handling: Use blocks and rescue clauses for graceful error handling:
- block:
- name: Install package
package:
name: mypackage
state: present
rescue:
- name: Handle installation failure
debug:
msg: "Failed to install package, continuing"
Documentation: Include comprehensive README.md with role description, requirements, and usage examples:
# Nginx Role
Installs and configures Nginx web server.
## Requirements
- Ansible 2.9+
- Python 2.7+ or 3.5+
## Variables
- `nginx_port`: Port to listen on (default: 80)
- `nginx_worker_processes`: Number of worker processes (default: auto)
## Example
```yaml
- hosts: webservers
roles:
- role: nginx
vars:
nginx_port: 8080
## Conclusion
Ansible roles provide the foundation for scalable, maintainable infrastructure automation. By following the standardized directory structure, leveraging Ansible Galaxy for community content, using collections for better organization, properly managing dependencies, and thoroughly testing with Molecule, you create automation that is reliable, reusable, and production-ready. Start with well-structured roles, share them through Galaxy, and build a library of automation components that accelerate infrastructure management across your organization.


