Molecule
Molecule is the standard harness for testing a role against a real, disposable target. From one command it spins up a fresh container or cloud instance, applies the role to it, verifies the result, proves idempotency, and tears the target down. It is a community project, not part of ansible-core, installed separately with pip install molecule molecule-plugins — and it is how you test the larkspur_web role against a clean Ubuntu 22.04 box instead of against your already-configured laptop.
The whole point is a throwaway target you did not set up. A green Molecule run proves the role builds the end state from nothing, because the box started with nothing — no residue from a previous run, no package the warm dev machine happened to already have. That is a stronger claim than "it ran clean here," and the rest of this topic is how Molecule makes it.
The Throwaway Target
Per run, Molecule creates an ephemeral host — a geerlingguy/docker-ubuntu2204-ansible container, a Podman container, or an EC2 instance — applies the role, and then destroys it. Because the target starts clean every time, "it works" means the role itself produces the state, not some leftover from a run you have forgotten. The disposability is the feature: a target you cannot accidentally pre-warm is a target whose result you can trust.
This is the precise gap that a green run on your laptop cannot close. Your laptop already has the package installed, the user created, the directory present — so a role that silently depends on that residue passes for you and fails on the first fresh node. Molecule removes the residue by removing the host, every single run.
The Scenario Layout
A role under Molecule gets a molecule/ directory holding one or more scenarios. The default scenario, molecule/default/, contains three files: molecule.yml (the driver, the platforms, and provisioner config), converge.yml (the playbook that applies the role under test), and verify.yml (the assertions that decide whether the converged target is right). Extra scenarios — molecule/podman/, molecule/rhel/ — test other targets or other settings alongside the default.
molecule/default/molecule.yml — driver, platform image, and verifierdriver: name: docker platforms: - name: larkspur-web-instance image: geerlingguy/docker-ubuntu2204-ansible:latest command: /lib/systemd/systemd # systemd-capable image so the service task is honest cgroupns_mode: host privileged: true provisioner: name: ansible verifier: name: ansible # run verify.yml; testinfra is the other choice (topic 72)
The converge.yml below is the playbook Molecule applies to that target — it simply includes the role under test, exactly as a real playbook would. Keeping it minimal matters: the converge play exercises the role, nothing more, so a failure points at the role and not at scenario glue.
molecule/default/converge.yml — apply the role to the throwaway target- name: Converge hosts: all become: true tasks: - name: Apply the larkspur_web role ansible.builtin.include_role: name: larkspur_web
The Sequence: converge / idempotence / verify / destroy
Molecule exposes the lifecycle as discrete subcommands. molecule converge applies the role to the live target. molecule idempotence runs converge a second time and fails on any changed. molecule verify runs the checks in verify.yml. molecule destroy tears the target down. Each one does exactly one thing, which is what lets you call them individually while iterating.
The combined command molecule test runs the full sequence as one gated pipeline: create, then converge, then idempotence, then verify, then destroy. Any step failing fails the whole run, and the target is destroyed at the end regardless — so molecule test is the honest gate, because it forces a clean create and a real teardown around the converge and the checks.
molecule test lifecycle — one gated sequence# inner loop: apply once, iterate without recreating the target molecule converge # the second-run gate: re-converge and fail on any changed molecule idempotence # run the assertions in verify.yml against the converged host molecule verify # the full honest gate: create -> converge -> idempotence -> verify -> destroy molecule test
Drivers
The driver decides what the target actually is. docker and podman give you fast, cheap, OS-pinned containers — the default for role unit tests, and enough to exercise the larkspur_web role's package, template, and config logic. A cloud or delegated driver stands up a real VM or instance, which you need when the role touches kernel modules, drives systemd in ways a container fakes badly, or calls real cloud APIs.
The split is honest about what each can prove. A container covers the parts of larkspur_web that are filesystem and package work; a VM covers the parts a container cannot run truthfully. A service task that is green against a container's faked init can still fail on a real host, so any role whose correctness depends on real service management wants a systemd-capable image or a VM driver, not a bare container.
Inner Loop vs Full Run
During development you run molecule converge repeatedly. It applies the role to the existing target without recreating it, so iterating on a template or a task is fast — edit, converge, look at the result, repeat, all against the same warm container. This is the inner loop, optimized for speed, and you live in it while the role is taking shape.
Before you push, you run the full molecule test, which always destroys and recreates the target. That recreation is the point: the inner loop's warm container may have accumulated state across a dozen converges, hiding a create-time bug like a missing dependency the warm box already had. The full run starts from clean and is the gate; the inner loop is for going fast, the full run for being sure.
- Running
molecule convergeto iterate and never running the fullmolecule test, so the role is never proven against a freshly-created target and a create-time bug — a missing dependency the warm container already had — ships uncaught. - Testing
larkspur_webonly in a Docker container and assuming systemd-dependent behavior is covered, when a container fakessystemdand theservicetask that is green in Molecule fails on a real host; use a systemd-enabled image or a VM driver for those paths. - Forgetting Molecule is not
ansible-coreand expecting it preinstalled in an execution environment, so CI fails atmolecule: command not found; it is a separate dependency that must be in the EE or the CI image. - Leaving
molecule destroyout of the loop on a cloud driver and accumulating orphaned EC2 instances that quietly bill, because the throwaway target was never thrown away. - Putting the role's own dependencies only in the warm dev container and not in
converge.ymlorrequirements.yml, so the role passes locally and fails the moment Molecule creates a clean target without them.
- Give every published or shared role a
molecule/default/scenario and run the fullmolecule testin CI, so each role carries its own proof that it converges a clean target. - Match the Molecule platform image to the real target OS — Ubuntu 22.04 for the Larkspur web tier — and add scenarios for any second OS the role claims to support, since a role untested on an OS does not support it.
- Use a systemd-capable image or a VM driver for any role whose correctness depends on real service management, rather than trusting a container's faked init.
- Iterate with
molecule convergefor speed but gate the push onmolecule test, which forces the create-from-clean-and-destroy cycle that catches the bugsconvergealone hides.
Knowledge Check
Why does a green Molecule run prove more than a green run on your laptop?
- Molecule creates a fresh throwaway target each run, so success means the role built the end state from nothing rather than relying on pre-existing residue
- Molecule applies the role many times over in a tight loop, and the sheer number of repeated identical passes is what raises the statistical confidence in the converge result
- Molecule disables the role's idempotency checks during the run, so a far larger share of its tasks report success
- Molecule runs the converge with root and elevated kernel privileges that an ordinary laptop session lacks
What does molecule test run, and in what order?
- The full sequence — create, converge, idempotence, verify, destroy — as one gated run that tears the target down at the end
- Only converge and verify, deliberately leaving the target host running afterward for manual inspection
- Verify first and then converge, so the assertions written in
verify.ymldefine the desired end state that the later converge step is then obliged to reach - A single apply of the role to the target with no idempotency check and no teardown step at the end
When is a Docker container driver insufficient and a VM driver needed?
- When the role's correctness depends on real service management or kernel behavior a container fakes badly, where a
servicetask is green in the container but fails on a real host - Whenever the role under test installs more than one package, since a container can only manage a single package install
- Only when testing the role on Windows targets, since any Linux distro always works correctly inside a container
- Never, because a container can faithfully reproduce anything a full VM can, including a real init system and kernel modules, so the VM driver is a legacy option nobody actually needs anymore
Your CI job fails with molecule: command not found. What is the most likely cause?
- Molecule is a separate community tool, not part of ansible-core, so it must be installed in the execution environment or CI image
- The role is missing its
molecule/default/scenario directory, so the runner cannot locate themoleculecommand to invoke and aborts before it reaches the converge step - The installed
ansible-coreon the runner is too new, and a recent release dropped the previously bundled molecule command that older versions had once shipped on the PATH - The Docker container driver is unavailable on the runner, which disables the
moleculebinary on the PATH until a working container engine is installed first
You got correct