Ansible Molecule Docker: Test Roles in Containers (Guide)
By Luca Berton · Published 2024-01-01 · Category: installation
How to test Ansible roles with Molecule and Docker. Configure platforms, write tests, multi-distro testing, and CI/CD integration with step-by-step examples.

Introduction
In the realm of DevOps and infrastructure as code (IaC), testing automation is a critical component to ensure the reliability and efficiency of configurations. Ansible, a powerful open-source automation tool, allows users to define and manage infrastructure as code through playbooks. Molecule, an extension of Ansible, facilitates the testing of Ansible roles in an isolated environment, providing a consistent and reproducible testing workflow.
One common scenario involves using Docker containers as test hosts within Molecule. Docker containers offer lightweight, portable environments that can be easily spun up and torn down, making them ideal for testing purposes. In this article, we will explore a Molecule setup utilizing Docker containers for the create, converge, and destroy steps.
See also: Ansible Molecule: Test Roles & Collections in Containers (Guide)
Molecule Configuration
The Molecule configuration is defined in themolecule.yml file. This configuration specifies the test platforms and dependencies. In this example, the dependency is set to the Galaxy role, and the platforms include a Docker container based on the Ubuntu 22.04 image.
dependency:
name: galaxy
options:
requirements-file: requirements.yml
platforms:
- name: molecule-ubuntu
image: ubuntu:22.04
The requirements.yml file lists the necessary Ansible collections, in this case, the community.docker collection.
collections:
- community.docker
Create Playbook
Thecreate.yml playbook is responsible for creating Docker containers based on the defined platforms. It uses the community.docker.docker_container Ansible module to start containers with a specified image and other parameters. If the containers are not running, the playbook fails, and detailed information is printed.
The playbook also dynamically adds the created containers to the Molecule inventory.
- name: Create
hosts: localhost
gather_facts: false
vars:
molecule_inventory:
all:
hosts: {}
molecule: {}
tasks:
- name: Create a container
community.docker.docker_container:
name: "{{ item.name }}"
image: "{{ item.image }}"
state: started
command: sleep 1d
log_driver: json-file
register: result
loop: "{{ molecule_yml.platforms }}"
- name: Print some info
ansible.builtin.debug:
msg: "{{ result.results }}"
- name: Fail if container is not running
when: >
item.container.State.ExitCode != 0 or
not item.container.State.Running
ansible.builtin.include_tasks:
file: tasks/create-fail.yml
loop: "{{ result.results }}"
loop_control:
label: "{{ item.container.Name }}"
- name: Add container to molecule_inventory
vars:
inventory_partial_yaml: |
all:
children:
molecule:
hosts:
"{{ item.name }}":
ansible_connection: community.docker.docker
ansible.builtin.set_fact:
molecule_inventory: >
{{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
loop: "{{ molecule_yml.platforms }}"
loop_control:
label: "{{ item.name }}"
- name: Dump molecule_inventory
ansible.builtin.copy:
content: |
{{ molecule_inventory | to_yaml }}
dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
mode: "0600"
- name: Force inventory refresh
ansible.builtin.meta: refresh_inventory
- name: Fail if molecule group is missing
ansible.builtin.assert:
that: "'molecule' in groups"
fail_msg: |
molecule group was not found inside inventory groups: {{ groups }}
run_once: true # noqa: run-once[task]
- name: Validate that inventory was refreshed
hosts: molecule
gather_facts: false
tasks:
- name: Check uname
ansible.builtin.raw: uname -a
register: result
changed_when: false
- name: Display uname info
ansible.builtin.debug:
msg: "{{ result.stdout }}"
See also: Ansible Troubleshooting: Fix ModuleNotFoundError 'ansible'
Converge Playbook
Theconverge.yml playbook checks for the existence of the "molecule" group in the inventory and then performs the convergence steps on the created containers.
- name: Fail if molecule group is missing
hosts: localhost
tasks:
- name: Print some info
ansible.builtin.debug:
msg: "{{ groups }}"
- name: Assert group existence
ansible.builtin.assert:
that: "'molecule' in groups"
fail_msg: |
molecule group was not found inside inventory groups: {{ groups }}
- name: Converge
hosts: molecule
gather_facts: false
tasks:
- name: Check uname
ansible.builtin.raw: uname -a
register: result
changed_when: false
- name: Print some info
ansible.builtin.assert:
that: result.stdout | regex_search("^Linux")
Destroy Playbook
The destroy.yml playbook is responsible for stopping and removing the Docker containers and cleaning up the dynamic Molecule inventory.
- name: Destroy molecule containers
hosts: molecule
gather_facts: false
tasks:
- name: Stop and remove container
delegate_to: localhost
community.docker.docker_container:
name: "{{ inventory_hostname }}"
state: absent
auto_remove: true
- name: Remove dynamic molecule inventory
hosts: localhost
gather_facts: false
tasks:
- name: Remove dynamic inventory file
ansible.builtin.file:
path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
state: absent
See also: Ansible Automation: Complete Guide to IT Automation with Playbook Examples
Links
• https://ansible.readthedocs.io/projects/molecule/Conclusion
In conclusion, using Docker containers with Molecule provides an efficient and isolated testing environment for Ansible playbooks. The defined playbooks for create, converge, and destroy allow for a seamless testing workflow, ensuring that Ansible roles are robust and reliable in different scenarios. This setup not only enhances the development process but also contributes to the overall stability of infrastructure configurations managed by Ansible.
Setup
pip install molecule molecule-docker ansible-core
molecule.yml with Docker
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu2404
image: ubuntu:24.04
pre_build_image: true
tmpfs:
- /run
- /tmp
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
command: /lib/systemd/systemd
cgroupns_mode: host
privileged: true
- name: rocky9
image: rockylinux:9
pre_build_image: true
command: /lib/systemd/systemd
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
Multi-Distro Testing
platforms:
- name: ubuntu2204
image: ubuntu:22.04
pre_build_image: true
- name: ubuntu2404
image: ubuntu:24.04
pre_build_image: true
- name: debian12
image: debian:12
pre_build_image: true
- name: rocky9
image: rockylinux:9
pre_build_image: true
- name: fedora41
image: fedora:41
pre_build_image: true
converge.yml
---
- name: Converge
hosts: all
become: true
tasks:
- name: Include role
include_role:
name: my_role
verify.yml (Tests)
---
- name: Verify
hosts: all
become: true
tasks:
- name: Check nginx installed
command: nginx -v
changed_when: false
- name: Check nginx running
systemd:
name: nginx
register: nginx_service
- assert:
that: nginx_service.status.ActiveState == 'active'
- name: Check config syntax
command: nginx -t
changed_when: false
- name: Check port 80
wait_for: { port: 80, timeout: 5 }
Run Tests
# Full lifecycle
molecule test
# create → dependency → prepare → converge → idempotence → verify → destroy
# Development workflow
molecule create # Start containers
molecule converge # Apply role
molecule converge # Run again (test idempotency)
molecule verify # Run tests
molecule login -h ubuntu2404 # Debug in container
molecule destroy # Clean up
Custom Docker Images
platforms:
- name: systemd-ubuntu
image: "myregistry/ansible-test-ubuntu:24.04"
pre_build_image: true
dockerfile: Dockerfile.j2 # Or use pre-built
# Dockerfile for systemd support
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y systemd python3 sudo
CMD ["/lib/systemd/systemd"]
prepare.yml (Pre-setup)
---
- name: Prepare
hosts: all
become: true
tasks:
- name: Install Python (for raw images)
raw: apt-get update && apt-get install -y python3
when: ansible_os_family == "Debian"
changed_when: false
CI/CD Integration
# .github/workflows/molecule.yml
name: Molecule Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
distro: [ubuntu2404, rocky9, debian12]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- run: pip install molecule molecule-docker ansible-core
- run: molecule test
env:
MOLECULE_DISTRO: ${{ matrix.distro }}
PY_COLORS: '1'
Debugging Tips
# Keep container after failure
molecule test --destroy=never
# SSH into container
molecule login -h ubuntu2404
# View container logs
docker logs molecule-ubuntu2404
# Run specific stage
molecule converge # Just apply
molecule verify # Just test
FAQ
Why do containers need systemd?
If your role manages services (service or systemd module), the container needs systemd running. Use privileged: true and mount cgroups.
Molecule vs ansible-test?
Molecule tests roles in real environments (Docker, Vagrant, cloud). ansible-test is for collection unit/integration testing. Both are complementary.
Tests pass locally but fail in CI?
Check: Docker version differences, image availability, network access (for package installs), and resource limits.
Related Articles
• discovering content via Ansible Galaxy • Ansible loop_control Guide • fact-based conditionals in Ansible • building an Ansible inventory • Ansible Docker patternsCategory: installation