TL;DR
- Molecule provides automated testing for Ansible roles with Docker containers, multi-platform scenarios, and built-in idempotency checks
- The test lifecycle (
create → converge → idempotence → verify → destroy) catches configuration drift and non-idempotent tasks before production- Use Docker for fast iteration during development; switch to Vagrant only when testing kernel parameters or systemd-specific features
Best for: Teams with 5+ Ansible roles who want reproducible infrastructure and CI/CD integration Skip if: You only have a few simple playbooks (ansible-lint may be enough) Read time: 20 minutes
When your Ansible playbooks deploy to production, how confident are you that they’ll work correctly? This is where Molecule comes in—a testing framework designed specifically for Ansible roles that transforms infrastructure development from “it worked on my machine” to “it’s tested across Ubuntu 20.04, 22.04, and Debian 11.”
In this tutorial, you’ll learn everything from basic role testing to complex multi-scenario validations with CI/CD integration. Whether you’re managing a handful of servers or orchestrating thousands of nodes, Molecule will transform how you develop and maintain your infrastructure as code.
Understanding test case design techniques helps structure your Molecule scenarios effectively. This approach aligns well with continuous testing in DevOps practices. For comprehensive test visibility, consider integrating Molecule test results with Allure Framework.
AI-Assisted Approaches
Modern AI assistants can accelerate Molecule workflow development and troubleshooting. Here are practical prompts for common tasks:
Generating verify.yml tests from role tasks:
Analyze this Ansible role's tasks/main.yml and generate comprehensive
Molecule verify.yml tests. For each task, create assertions that verify:
1. Package installation states
2. Service running/enabled status
3. File existence and permissions
4. Configuration content validation
5. Port listening states
Role tasks:
[paste your tasks/main.yml]
Debugging idempotency failures:
My Molecule idempotency test shows these tasks as "changed" on second run:
[paste task output]
Explain why each task isn't idempotent and provide idempotent alternatives
using appropriate Ansible modules (lineinfile, template, blockinfile, etc.)
Creating multi-platform scenarios:
Generate a Molecule molecule.yml configuration that tests my role across:
- Ubuntu 20.04, 22.04, and 24.04
- Debian 11 and 12
- Rocky Linux 8 and 9
Include:
- Appropriate Docker images with systemd support
- Platform-specific variables for package manager differences
- Parallel execution hints for CI optimization
Generating Testinfra tests from existing verify.yml:
Convert these Ansible verify.yml assertions to Python Testinfra tests.
Maintain the same test coverage but use Testinfra's more Pythonic syntax.
Include proper fixtures and parametrization where applicable.
Current verify.yml:
[paste your verify.yml]
CI/CD pipeline integration:
Generate a GitHub Actions workflow for my Molecule-tested Ansible role that:
1. Runs ansible-lint and yamllint
2. Tests across Ubuntu and Debian matrix
3. Tests multiple Molecule scenarios
4. Caches pip dependencies between runs
5. Publishes to Ansible Galaxy on tag push
Current role structure: [describe your role]
When to Use Molecule
Migration Decision Framework
| Your Situation | Recommendation | Why |
|---|---|---|
| 1-3 simple playbooks, no roles | Stay with ansible-lint | Overhead doesn’t justify setup time |
| 5+ roles, single developer | Start with Molecule | ROI in catching errors before production |
| Team of 3+, shared roles | Molecule + CI required | Prevents “works on my machine” issues |
| Critical infrastructure roles | Molecule + multi-scenario | Test upgrades, edge cases, rollbacks |
| Publishing to Ansible Galaxy | Molecule mandatory | Community expectation, builds trust |
Consider Molecule When
- You have reusable roles: Molecule shines when testing roles that deploy across multiple projects or environments
- Multi-OS support needed: Testing on Ubuntu, Debian, and RHEL simultaneously catches OS-specific issues early
- CI/CD integration exists: Automated testing on every commit prevents regression
- Team collaboration: Multiple engineers modifying the same roles need consistent validation
- Compliance requirements: Auditable test results prove infrastructure meets standards
Consider Alternatives When
- One-off playbooks: Simple scripts that run once don’t need the overhead
- Learning Ansible basics: Focus on Ansible fundamentals first before adding testing complexity
- Extremely simple roles: A role that installs one package might be over-tested with Molecule
- Kernel-level testing needed: Vagrant or bare-metal testing may be more appropriate for kernel modules, network namespaces, or systemd-specific features
- Resource-constrained CI: Molecule containers add overhead; ansible-lint alone may suffice for simple validation
Prerequisites
Before diving into Molecule, ensure you have the following set up:
Required Tools:
- Python 3.8 or higher
- Ansible 2.10 or higher
- Docker (for container-based testing)
- pip (Python package manager)
Knowledge Requirements:
- Basic Ansible experience (roles, tasks, handlers)
- Familiarity with YAML syntax
- Understanding of Docker concepts
- Command-line proficiency
Environment Setup: I recommend creating a dedicated Python virtual environment to avoid dependency conflicts:
python3 -m venv molecule-env
source molecule-env/bin/activate # On Windows: molecule-env\Scripts\activate
Step 1: Installing Molecule
Molecule installation is straightforward with pip. You’ll need to install Molecule along with the Docker driver, which we’ll use for our testing scenarios.
# Install Molecule with Docker support
pip install "molecule[docker,lint]"
# Verify installation
molecule --version
Expected Output:
molecule 5.0.1 using python 3.11
ansible:2.15.4
default:5.0.1 from molecule
docker:2.1.0 from molecule_plugins.docker
The installation includes several components:
- Molecule core: The testing framework itself
- Docker driver: Enables container-based testing
- Lint tools: Ansible-lint and yamllint for code quality
Verification Checkpoint: Run molecule --version and confirm all components are installed. If Docker driver is missing, reinstall with the [docker] extra.
Step 2: Initializing a New Role
Let’s create a practical example: an Nginx web server role. Molecule integrates seamlessly with Ansible Galaxy’s role structure.
# Create a new role with Molecule testing built-in
molecule init role nginx --driver-name docker
# Navigate into the role directory
cd nginx
This command generates a complete role structure:
nginx/
├── defaults/
│ └── main.yml
├── files/
├── handlers/
│ └── main.yml
├── meta/
│ └── main.yml
├── molecule/
│ └── default/
│ ├── converge.yml
│ ├── molecule.yml
│ └── verify.yml
├── tasks/
│ └── main.yml
├── templates/
├── tests/
└── vars/
└── main.yml
Key Directories:
molecule/default/: Contains your test scenario configurationtasks/main.yml: Where your Ansible tasks gohandlers/main.yml: For service restarts and notifications
Step 3: Understanding Molecule Structure
The molecule/default/ directory contains three critical files:
molecule.yml - The main configuration file:
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: instance
image: geerlingguy/docker-ubuntu2204-ansible:latest
pre_build_image: true
provisioner:
name: ansible
verifier:
name: ansible
Configuration Breakdown:
dependency: Manages role dependencies from Ansible Galaxydriver: Specifies Docker for spinning up test instancesplatforms: Defines test environments (OS, image, resources)provisioner: Uses Ansible to apply your roleverifier: Runs verification tests after convergence
converge.yml - Applies your role to test instances:
---
- name: Converge
hosts: all
become: true
tasks:
- name: "Include nginx"
include_role:
name: "nginx"
verify.yml - Contains assertions to validate role behavior:
---
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Example assertion
ansible.builtin.assert:
that: true
Step 4: Writing Your First Test
Let’s build a functional Nginx role with proper tests. Start by defining the role tasks.
tasks/main.yml:
---
# Install Nginx web server
- name: Install Nginx package
ansible.builtin.apt:
name: nginx
state: present
update_cache: yes
notify: Start nginx
- name: Ensure Nginx is started and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: yes
- name: Deploy custom index.html
ansible.builtin.copy:
content: |
<html>
<head><title>Ansible Managed</title></head>
<body><h1>Deployed with Ansible</h1></body>
</html>
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: '0644'
handlers/main.yml:
---
- name: Start nginx
ansible.builtin.service:
name: nginx
state: started
Now create meaningful verification tests in molecule/default/verify.yml:
---
- name: Verify
hosts: all
gather_facts: true
tasks:
- name: Check if Nginx is installed
ansible.builtin.package_facts:
manager: auto
- name: Assert Nginx package is present
ansible.builtin.assert:
that:
- "'nginx' in ansible_facts.packages"
fail_msg: "Nginx package is not installed"
success_msg: "Nginx package is correctly installed"
- name: Check if Nginx service is running
ansible.builtin.service_facts:
- name: Assert Nginx service is active
ansible.builtin.assert:
that:
- "ansible_facts.services['nginx.service'].state == 'running'"
- "ansible_facts.services['nginx.service'].status == 'enabled'"
fail_msg: "Nginx service is not running or not enabled"
success_msg: "Nginx service is active and enabled"
- name: Check if Nginx is listening on port 80
ansible.builtin.wait_for:
port: 80
timeout: 5
- name: Verify custom index.html content
ansible.builtin.uri:
url: http://localhost
return_content: yes
register: webpage
- name: Assert webpage contains expected content
ansible.builtin.assert:
that:
- "'Deployed with Ansible' in webpage.content"
fail_msg: "Custom index.html was not deployed correctly"
success_msg: "Custom index.html is correctly deployed"
Step 5: Running Tests
Molecule provides a complete test lifecycle. Here’s how to execute tests:
# Run the full test sequence
molecule test
# Or run individual steps:
molecule create # Create test instances
molecule converge # Apply your role
molecule verify # Run verification tests
molecule destroy # Clean up instances
The Full Test Sequence:
When you run molecule test, Molecule executes these phases:
- Dependency: Install role dependencies from Ansible Galaxy
- Lint: Check syntax and best practices
- Cleanup: Remove previous test instances
- Destroy: Ensure clean state
- Create: Spin up test containers
- Prepare: Optional pre-configuration steps
- Converge: Apply your role
- Idempotence: Re-run to ensure no changes occur
- Verify: Run assertion tests
- Destroy: Clean up resources
Expected Output (abbreviated):
--> Test matrix
└── default
├── dependency
├── cleanup
├── destroy
├── create
├── prepare
├── converge
├── idempotence
├── verify
└── destroy
--> Scenario: 'default'
--> Action: 'converge'
PLAY [Converge] ********************************************************
TASK [Include nginx] ***************************************************
included: /Users/user/nginx/tasks/main.yml
TASK [Install Nginx package] *******************************************
changed: [instance]
TASK [Ensure Nginx is started and enabled] *****************************
changed: [instance]
PLAY RECAP *************************************************************
instance: ok=3 changed=2 unreachable=0 failed=0
--> Scenario: 'default'
--> Action: 'verify'
TASK [Assert Nginx service is active] **********************************
ok: [instance] => {
"msg": "Nginx service is active and enabled"
}
Development Workflow Tip: During development, use molecule converge repeatedly to apply changes without destroying instances. Only run the full molecule test when you’re ready to validate everything.
Step 6: Testing with Different Scenarios
Real-world infrastructure rarely runs on a single OS. Molecule scenarios let you test across multiple platforms simultaneously.
Create a multi-platform scenario by modifying molecule/default/molecule.yml:
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu-20-04
image: geerlingguy/docker-ubuntu2004-ansible:latest
pre_build_image: true
- name: ubuntu-22-04
image: geerlingguy/docker-ubuntu2204-ansible:latest
pre_build_image: true
- name: debian-11
image: geerlingguy/docker-debian11-ansible:latest
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: converge.yml
verify: verify.yml
verifier:
name: ansible
Creating Named Scenarios:
You can also create completely separate test scenarios for different use cases:
# Create a production-like scenario
molecule init scenario production
# Create a scenario for testing upgrades
molecule init scenario upgrade
This creates:
molecule/
├── default/
│ ├── converge.yml
│ ├── molecule.yml
│ └── verify.yml
├── production/
│ ├── converge.yml
│ ├── molecule.yml
│ └── verify.yml
└── upgrade/
├── converge.yml
├── molecule.yml
└── verify.yml
Running Specific Scenarios:
# Test only the production scenario
molecule test -s production
# Converge multiple scenarios
molecule converge --all
Step 7: CI/CD Integration
Molecule integrates seamlessly with continuous integration pipelines. Here’s how to set it up with GitHub Actions.
.github/workflows/molecule.yml:
---
name: Molecule Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
scenario:
- default
- production
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install "molecule[docker,lint]"
pip install ansible
- name: Run Molecule tests
run: molecule test -s ${{ matrix.scenario }}
GitLab CI Configuration (.gitlab-ci.yml):
---
stages:
- test
molecule:
stage: test
image: geerlingguy/docker-ubuntu2204-ansible:latest
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
before_script:
- pip install "molecule[docker,lint]"
script:
- molecule test --all
only:
- merge_requests
- main
Verification Checkpoint: Push your role to a Git repository and verify the CI pipeline executes successfully. Check that all scenarios pass and the pipeline fails on test failures.
Real-world Examples
Example 1: Testing a Web Server Role
Let’s extend our Nginx role with SSL configuration and virtual hosts.
tasks/main.yml (extended):
---
- name: Install Nginx and SSL dependencies
ansible.builtin.apt:
name:
- nginx
- openssl
state: present
update_cache: yes
- name: Create SSL directory
ansible.builtin.file:
path: /etc/nginx/ssl
state: directory
mode: '0755'
- name: Generate self-signed SSL certificate
ansible.builtin.command:
cmd: >
openssl req -x509 -nodes -days 365 -newkey rsa:2048
-keyout /etc/nginx/ssl/nginx.key
-out /etc/nginx/ssl/nginx.crt
-subj "/C=US/ST=State/L=City/O=Org/CN=localhost"
creates: /etc/nginx/ssl/nginx.crt
- name: Deploy SSL virtual host configuration
ansible.builtin.template:
src: vhost-ssl.conf.j2
dest: /etc/nginx/sites-available/default
mode: '0644'
notify: Reload nginx
- name: Ensure Nginx is started
ansible.builtin.service:
name: nginx
state: started
enabled: yes
templates/vhost-ssl.conf.j2:
server {
listen 80;
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
molecule/default/verify.yml (extended):
---
- name: Verify
hosts: all
gather_facts: true
tasks:
- name: Check SSL certificate exists
ansible.builtin.stat:
path: /etc/nginx/ssl/nginx.crt
register: ssl_cert
- name: Assert SSL certificate is present
ansible.builtin.assert:
that:
- ssl_cert.stat.exists
- ssl_cert.stat.mode == '0644'
- name: Verify Nginx is listening on HTTPS
ansible.builtin.wait_for:
port: 443
timeout: 5
- name: Test HTTPS connection
ansible.builtin.uri:
url: https://localhost
validate_certs: no
return_content: yes
register: https_response
- name: Assert HTTPS is working
ansible.builtin.assert:
that:
- https_response.status == 200
Example 2: Testing Database Configuration
Here’s how to test a PostgreSQL role with Molecule.
molecule/default/molecule.yml:
---
platforms:
- name: postgres-instance
image: geerlingguy/docker-ubuntu2204-ansible:latest
pre_build_image: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
privileged: true
command: /lib/systemd/systemd
tasks/main.yml (PostgreSQL role):
---
- name: Install PostgreSQL
ansible.builtin.apt:
name:
- postgresql
- postgresql-contrib
- python3-psycopg2
state: present
update_cache: yes
- name: Ensure PostgreSQL is started
ansible.builtin.service:
name: postgresql
state: started
enabled: yes
- name: Create application database
become_user: postgres
community.postgresql.postgresql_db:
name: appdb
state: present
- name: Create database user
become_user: postgres
community.postgresql.postgresql_user:
name: appuser
password: "{{ db_password | default('changeme') }}"
db: appdb
priv: ALL
state: present
molecule/default/verify.yml:
---
- name: Verify
hosts: all
become: true
tasks:
- name: Check PostgreSQL service status
ansible.builtin.service_facts:
- name: Assert PostgreSQL is running
ansible.builtin.assert:
that:
- "ansible_facts.services['postgresql.service'].state == 'running'"
- name: Verify database exists
become_user: postgres
community.postgresql.postgresql_query:
db: appdb
query: SELECT 1
register: db_check
- name: Assert database is accessible
ansible.builtin.assert:
that:
- db_check is succeeded
- name: Test database connection with user
become_user: postgres
community.postgresql.postgresql_query:
db: appdb
login_user: appuser
query: CREATE TABLE test (id serial PRIMARY KEY, name varchar(50))
register: table_creation
- name: Assert user has correct privileges
ansible.builtin.assert:
that:
- table_creation is succeeded
Example 3: Testing Security Hardening
Security roles require thorough testing to ensure they don’t break functionality.
molecule/default/verify.yml (security hardening):
---
- name: Verify Security Hardening
hosts: all
gather_facts: true
tasks:
- name: Check if SSH password authentication is disabled
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
check_mode: yes
register: ssh_config
- name: Assert SSH is properly configured
ansible.builtin.assert:
that:
- not ssh_config.changed
fail_msg: "SSH password authentication is not disabled"
- name: Verify firewall is active
ansible.builtin.command: ufw status
register: ufw_status
changed_when: false
- name: Assert firewall is enabled
ansible.builtin.assert:
that:
- "'Status: active' in ufw_status.stdout"
- name: Check for unattended upgrades
ansible.builtin.stat:
path: /etc/apt/apt.conf.d/50unattended-upgrades
register: unattended_upgrades
- name: Assert automatic updates are configured
ansible.builtin.assert:
that:
- unattended_upgrades.stat.exists
- name: Verify root login is disabled
ansible.builtin.command: passwd -S root
register: root_status
changed_when: false
- name: Assert root account is locked
ansible.builtin.assert:
that:
- "'L' in root_status.stdout"
fail_msg: "Root account is not locked"
Best Practices
Test Organization
Keep tests focused and modular:
# Bad: One giant verification file
verify.yml # 500 lines of tests
# Good: Separate verification tasks
molecule/default/
├── molecule.yml
├── converge.yml
├── verify.yml
└── verify/
├── test_installation.yml
├── test_configuration.yml
├── test_security.yml
└── test_networking.yml
Main verify.yml includes specific tests:
---
- name: Verify
hosts: all
tasks:
- name: Run installation tests
include_tasks: verify/test_installation.yml
- name: Run configuration tests
include_tasks: verify/test_configuration.yml
- name: Run security tests
include_tasks: verify/test_security.yml
Scenario Management
Use scenarios strategically:
default: Basic functionality testingproduction: Production-like environment with all featuresupgrade: Testing upgrade paths from previous versionsmulti-node: Distributed system testing
Don’t create scenarios for minor variations. Instead, use variables:
# molecule/default/converge.yml
- name: Converge
hosts: all
vars:
# Override these in specific scenarios
enable_ssl: "{{ scenario_ssl | default(true) }}"
enable_monitoring: "{{ scenario_monitoring | default(false) }}"
roles:
- nginx
Idempotency Testing
Idempotency is critical—running your role multiple times should not cause changes after the first run.
Molecule automatically tests this. Ensure your tasks are idempotent:
# Bad: Always reports changed
- name: Configure application
ansible.builtin.shell: echo "config=true" >> /etc/app.conf
# Good: Idempotent configuration
- name: Configure application
ansible.builtin.lineinfile:
path: /etc/app.conf
line: "config=true"
create: yes
Monitor idempotence test output:
PLAY RECAP *****************************************************************
instance: ok=10 changed=0 unreachable=0 failed=0
If changed is greater than 0 on the second run, you have idempotency issues.
Docker vs Vagrant
Docker (Recommended for most cases):
- Fast: Containers start in seconds
- Resource-efficient: Run many instances simultaneously
- CI-friendly: Works in most CI environments
- Limitations: Cannot test kernel modules, systemd quirks, or anything requiring full VM
Vagrant (For complex scenarios):
- Full VMs: Complete operating system
- Systemd: Full init system support
- Kernel testing: Can test kernel parameters and modules
- Trade-offs: Slower (minutes to start), resource-heavy, harder in CI
When to use Vagrant:
# molecule/vagrant/molecule.yml
---
driver:
name: vagrant
platforms:
- name: ubuntu-vm
box: ubuntu/jammy64
memory: 2048
cpus: 2
provisioner:
name: ansible
Use Vagrant when testing roles that:
- Modify kernel parameters (
sysctl) - Require specific systemd features
- Install kernel modules
- Need full networking stack
Common Pitfalls
Container Limitations
Problem: Systemd doesn’t work properly in standard containers.
Solution: Use pre-built images with systemd support:
platforms:
- name: instance
image: geerlingguy/docker-ubuntu2204-ansible:latest
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
privileged: true
command: /lib/systemd/systemd
Jeff Geerling’s Docker images are specifically built for Ansible testing with working systemd.
Privilege Issues
Problem: Tasks fail with permission errors.
Solution: Use become appropriately in your converge playbook:
# molecule/default/converge.yml
---
- name: Converge
hosts: all
become: true # Run tasks with sudo
tasks:
- name: Include role
include_role:
name: nginx
Avoid requiring become: yes in the role itself when possible. Let the user decide privilege escalation.
Dependency Management
Problem: Role depends on other roles, but they’re not available during testing.
Solution: Define dependencies in meta/main.yml:
# meta/main.yml
---
dependencies:
- role: geerlingguy.security
- role: geerlingguy.firewall
Molecule automatically installs these before testing if you have the Galaxy dependency manager enabled:
# molecule/default/molecule.yml
dependency:
name: galaxy
options:
role-file: requirements.yml
Create requirements.yml for external dependencies:
# requirements.yml
---
roles:
- name: geerlingguy.security
version: 2.3.0
- name: geerlingguy.firewall
version: 3.1.0
Test Flakiness
Problem: Tests occasionally fail for no apparent reason.
Common causes:
- Network timeouts
- Race conditions in service startup
- Insufficient wait times
Solution: Add appropriate waits and retries:
- name: Wait for service to be ready
ansible.builtin.wait_for:
port: 80
delay: 2
timeout: 30
- name: Check endpoint with retry
ansible.builtin.uri:
url: http://localhost/health
register: health_check
until: health_check.status == 200
retries: 5
delay: 3
Tools and Integration
Testinfra for Verification
While Molecule uses Ansible for verification by default, you can also use Testinfra (Python-based testing):
pip install testinfra
molecule/default/tests/test_default.py:
import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']
).get_hosts('all')
def test_nginx_is_installed(host):
nginx = host.package("nginx")
assert nginx.is_installed
def test_nginx_running_and_enabled(host):
nginx = host.service("nginx")
assert nginx.is_running
assert nginx.is_enabled
def test_nginx_listening(host):
assert host.socket("tcp://0.0.0.0:80").is_listening
def test_index_html_content(host):
content = host.file("/var/www/html/index.html").content_string
assert "Deployed with Ansible" in content
Configure Testinfra in molecule.yml:
verifier:
name: testinfra
options:
v: 1
GitLab/GitHub CI Integration
Advanced GitHub Actions with matrix testing:
---
name: Ansible Role Testing
on:
push:
branches: [main]
pull_request:
schedule:
- cron: '0 0 * * 0' # Weekly
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: pip install ansible-lint yamllint
- name: Lint Ansible role
run: ansible-lint .
- name: Lint YAML files
run: yamllint .
test:
needs: lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
distro:
- ubuntu2004
- ubuntu2204
- debian11
scenario:
- default
- production
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Molecule
run: pip install "molecule[docker,lint]" ansible
- name: Run Molecule test
run: molecule test -s ${{ matrix.scenario }}
env:
PY_COLORS: '1'
ANSIBLE_FORCE_COLOR: '1'
MOLECULE_DISTRO: ${{ matrix.distro }}
Ansible Galaxy Integration
When publishing roles to Ansible Galaxy, include Molecule tests to boost credibility:
# .github/workflows/release.yml
---
name: Release to Galaxy
on:
push:
tags:
- 'v*'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Test with Molecule
run: |
pip install "molecule[docker,lint]"
molecule test
release:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Release to Galaxy
uses: robertdebock/galaxy-action@1.2.0
with:
galaxy_api_key: ${{ secrets.GALAXY_API_KEY }}
Link to your CI badge in README.md:
# Ansible Role: Nginx
[](https://github.com/username/ansible-role-nginx/actions)
[](https://galaxy.ansible.com/username/nginx)
Tested with Molecule on:
- Ubuntu 20.04
- Ubuntu 22.04
- Debian 11
Conclusion
Molecule transforms Ansible role development from a manual, error-prone process into a robust, automated workflow. By implementing comprehensive testing strategies—from basic functionality checks to multi-platform scenarios—you ensure your infrastructure code is reliable, maintainable, and production-ready.
Key Takeaways:
- Always test your roles across multiple operating systems and scenarios
- Leverage idempotency testing to prevent configuration drift
- Integrate Molecule into your CI/CD pipeline for continuous validation
- Use Docker for speed during development, Vagrant for complex system-level testing
- Write clear, focused verification tests that act as living documentation
Next Steps:
- Explore advanced scenarios: Implement multi-node testing for distributed systems (learn more in our Docker Container Management Best Practices guide)
- Integrate with your CI/CD pipeline: Add Molecule tests to your existing pipelines (check out CI/CD Pipeline Optimization Strategies)
- Contribute to the community: Share your tested roles on Ansible Galaxy
- Learn complementary testing tools: Explore Testinfra, InSpec, and ServerSpec for deeper infrastructure validation
For more on infrastructure automation and DevOps best practices, explore our comprehensive guide on Infrastructure as Code with Terraform or dive into Kubernetes Deployment Strategies for cloud-native infrastructure testing.
Additional Resources:
- Official Molecule Documentation
- Ansible Testing Strategies Guide
- Jeff Geerling’s Ansible Testing Series
Start testing your infrastructure code today—your future self (and your team) will thank you when deployments become predictable, reliable, and stress-free.
Official Resources
See Also
- Infrastructure as Code Testing - Validation strategies for Terraform and Ansible
- Continuous Testing in DevOps - Integrate infrastructure testing into CI/CD pipelines
- Terraform Testing Strategies - Complementary IaC testing approaches
- Kubernetes Testing Strategies - Container orchestration testing for cloud-native infrastructure
- CI/CD Pipeline Optimization - Optimize Molecule tests in your delivery pipeline
