Beyond ‘Works on My Machine’: Testing Ansible Playbooks with Molecule and Docker

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

The 4:45 PM Friday Deployment Trap

I remember a specific Friday afternoon when a minor Nginx configuration change took down our entire staging environment. On my local machine, the Ansible playbook ran flawlessly against a fresh Ubuntu VM. However, the staging server had a slightly older kernel and a conflicting legacy package I didn’t know existed. This is the classic trap of Infrastructure as Code (IaC). We automate the deployment, but we forget to automate the testing of the automation itself.

Relying on manual spot-checks or “testing in production” is high-risk. As your infrastructure grows to dozens or hundreds of nodes, you can’t afford to guess. You need a process that spins up a clean environment, applies your configuration, verifies the state, and then wipes everything clean. Combining Molecule with Docker provides exactly that level of certainty.

The Hidden Cost of Testing on Static Servers

Many teams start by testing playbooks against a dedicated, long-running development server. While this feels convenient at first, it creates several technical bottlenecks:

  • Configuration Drift: Over time, test servers accumulate “cruft”—manual tweaks, old packages, and temp files. Your playbook might only work because of a manual change you made six months ago.
  • State Contamination: Testing a database role might leave behind files that interfere with a web server role test.
  • Slow Feedback: Waiting 5 to 10 minutes for a full VM to boot kills developer momentum.
  • False Positives: Without a clean slate, it is nearly impossible to verify if your playbook is truly idempotent.

The core problem is the lack of a reproducible, ephemeral environment. If you cannot guarantee a “zero-state” for every test run, your results are essentially noise.

Comparing the Options: Manual VMs vs. Vagrant vs. Molecule

I’ve experimented with various workflows over the years. While each has a specific use case, the efficiency gap is significant.

1. Manual VM Snapshots

You create a VM, take a snapshot, run the code, and revert. This is painfully slow. It also fails to integrate with CI/CD pipelines, making it a non-starter for modern teams.

2. Vagrant

Vagrant mimics production hardware well, but it is resource-heavy. Running a three-node cluster test can easily consume 6GB of RAM and make a laptop fan sound like a jet engine. It’s also notoriously difficult to run inside cloud-based CI runners like GitHub Actions without nested virtualization.

3. Molecule with Docker

Molecule is a purpose-built framework for testing Ansible roles. When you pair it with Docker, the speed is unmatched. You can spin up a container, run your role, and verify the results in under 60 seconds. After implementing this workflow for a major client, we saw our “emergency” hotfixes for infrastructure roles drop by nearly 70%.

The Professional Workflow: Molecule + Docker

To follow along, ensure you have Python and Docker installed. I highly recommend using a Python virtual environment to avoid dependency conflicts with your system tools.

1. Setting Up the Environment

# Initialize your environment
python3 -m venv venv
source venv/bin/activate

# Install Molecule and the Docker driver
pip install "molecule-plugins[docker]" ansible-lint pytest-testinfra

2. Initializing a New Role

Molecule can generate the necessary scaffolding for you. If you are working on an existing role, run this command inside your role’s root directory:

molecule init scenario default --driver-name docker

This creates a molecule/default directory. Inside, the molecule.yml file defines your test platforms, while verify.yml handles the final checks.

3. Configuring the Test Platform

Open molecule/default/molecule.yml. Here, you define which OS distributions to test against. Testing against multiple versions simultaneously is a great way to catch compatibility bugs early.

dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: ubuntu-2204
    image: geerlingguy/docker-ubuntu2204-ansible:latest
    pre_build_image: true
    privileged: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
provisioner:
  name: ansible
verifier:
  name: ansible

Pro Tip: Use the geerlingguy images. They are pre-configured with systemd, which allows you to test services like Nginx or MySQL that normally fail in standard Docker containers.

4. Executing the Test Cycle

You can trigger the entire automated sequence with one command:

molecule test

Molecule will then handle the heavy lifting. It lints your code for syntax errors and creates the Docker instances. It runs your playbook (Converge) and then runs it again to check for idempotence. Finally, it executes your verification scripts and destroys the containers.

Advanced Strategies for Reliable Automation

Writing a test is easy; writing a test that provides real value requires a bit more strategy.

Mastering Idempotence

The idempotence check is Molecule’s most powerful feature. If your playbook reports a “changed” status on the second run, your automation is technically broken. It means you are running a script rather than defining a state. If you must use shell or command modules, always use the creates or removes arguments to ensure they only run when necessary.

Deep Verification with Testinfra

While verify.yml is convenient, pytest-testinfra allows you to write actual Python code to inspect your server. This is much more expressive and provides better error reporting.

def test_nginx_config(host):
    conf = host.file("/etc/nginx/nginx.conf")
    assert conf.user == "www-data"
    assert conf.contains("worker_connections 768;")

def test_port_80_is_listening(host):
    socket = host.socket("tcp://0.0.0.0:80")
    assert socket.is_listening

CI/CD Integration

Running Molecule locally is great for development, but the real power comes from integration. Set up a GitHub Action to run molecule test on every Pull Request. This creates a quality gate that prevents broken automation from ever reaching your main branch.

Final Thoughts

Professional automation is about more than just knowing Ansible syntax. It is about building a system you can trust. By adopting Molecule and Docker, you move from “hope-based” deployments to a validated, engineering-first approach. It might take an extra hour to configure your first test suite, but it will save you dozens of hours in troubleshooting later. Pick your most critical role and initialize Molecule today—your future self will appreciate the extra sleep.

Share: