AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

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.

Ansible Molecule Docker: Test Roles in Containers (Guide)

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 the molecule.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

The create.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

The converge.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 GalaxyAnsible loop_control Guidefact-based conditionals in Ansiblebuilding an Ansible inventoryAnsible Docker patterns

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home