Skip to main content
RunBook Academy

AnsibleXXXVIII · Git Workflow and CI for AnsibleAutomation as production code

The complete pipeline

Advanced⏱ ~22 minansible-coreansible-lintyamllintmolecule

What you'll learn

  • Order the pipeline stages so each one catches a class the previous cannot
  • Annotate every gate with the production failure it prevents, and drop the ones with no answer
  • Decide deliberately which gates block a merge and which only report
  • Design a degradation path so an urgent fix is not blocked by a slow or broken stage

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

Six lessons have introduced gates one at a time. This one puts them in order and makes the two decisions that turn a list of tools into a pipeline: what each stage is for, and what happens when it fails.

The organising rule is the one this part has used throughout. Every stage must be able to name the class of production failure it prevents. A stage with no answer to that is not a safety measure; it is a tax on every change, and it will eventually be removed by someone who is right to remove it.

The pipeline, annotated

Ordered cheapest first, because a stage that takes two seconds should never wait behind one that takes six minutes.

#StageTypical timeWhat it catches that the previous cannotProduction failure it prevents
1yamllint< 5sFormatting the parser accepts but humans misreadReview noise hiding a scope change
2ansible-playbook --syntax-check< 10sA playbook that cannot be assembled: bad keyword, missing role, missing importA run that dies at 02:00 having done half the estate
3Secret scan10–30sA credential in the diff or the historyA disclosed credential and a rotation incident
4Vault-encryption gate< 5sA vault.yml that is plaintext, or carries the wrong vault idProduction secrets readable by everyone with dev credentials
5ansible-inventory --graph per environment< 10sBroken inventory, unparseable group, wrong vault idA deploy that fails at variable load against production
6ansible-lint --profile production30–90sSemantic defects: non-idempotent commands, latest packages, missing FQCNA task that reports changed forever, restarting services nightly
7Molecule, path-filtered2–10 minRun-time behaviour: does the role work from nothing, and twiceA role that appends a line per run, or fails on a fresh host
8--check --diff against staging1–5 minWhat this change would actually do, on real hosts, with this environment’s variablesA change whose effect nobody could see before it landed
9Human review with that evidenceIntent, scope, rollback, timingCorrect automation aimed at the wrong machines

Stage 5 is the one people leave out and it is worth its ten seconds. It is the only static stage that loads and decrypts group_vars, so it is the only one that fails on a wrong vault id — --syntax-check exits 0 in that case, measured on 2.21.3.

Stages 1 to 6 are the ones you can express as a script and run identically on a laptop and in CI, which is what stops a pipeline failure from being a surprise:

Read-only / Safescripts/static-gates.sh — every stage that touches no host
#!/usr/bin/env bash
set -euo pipefail

yamllint -s -f parsable .

for env in development staging production; do
ansible-playbook -i "inventories/$env" playbooks/site.yml --syntax-check
done

scripts/scan-secrets.sh
scripts/check-vault-encrypted.sh

for env in development staging production; do
ansible-inventory -i "inventories/$env" --graph > /dev/null
done

ansible-lint --profile production

The vault ids for stages 2 and 5 come from the runner’s environment; note that the loop deliberately covers every environment, because an inventory that only breaks for production is the one that breaks at the worst moment.

Blocking or reporting

Not every gate should stop a merge, and getting this wrong in either direction is expensive.

Block on: syntax check, secret scan, vault-encryption gate, inventory load, lint at the chosen profile, and Molecule for the roles the change touches. These are all deterministic — they fail because the change is wrong, not because the world moved.

Report on: the check-mode diff. Its job is to produce evidence, and it depends on a staging environment that can be down for reasons unrelated to the change. Blocking on it means a staging outage blocks every merge in the repository, which teaches everyone to use the override. Attach the output, mark the stage failed if it errored, and let the human decide whether they have enough to review.

Report on: anything flaky, until it is not. A gate that fails spuriously once a week trains people to re-run rather than read, and a re-run habit applies equally to the real failures.

Making it fast enough not to be bypassed

The pipeline competes with an alternative that is always available: merging without it. Every minute it costs makes that alternative more attractive, and the moment it wins is an incident.

Four things buy the most time back:

Path filtering. A change under roles/webserver/ runs the webserver Molecule scenarios and nothing else. This is the single biggest win in the whole pipeline and it is a few lines of configuration.

Parallel stages. Stages 1 through 6 are independent of one another. Run them concurrently and the static portion costs as long as its slowest member rather than their sum.

Cached dependencies. ansible-galaxy install -r requirements.yml against a network on every job is both slow and a source of failures unrelated to the change. Cache it keyed on the file’s hash — which works correctly precisely because the versions are pinned.

Scheduled full runs. The complete platform matrix, every role, every scenario, nightly against the default branch. It catches the cross-role and upstream-image regressions that path filtering misses, without putting hours into the merge path.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Why does an ansible-inventory --graph stage earn a place alongside --syntax-check, which is faster and already runs?

  2. Q2. Which stages should report rather than block a merge? Select all that apply.

  3. Q3. A pipeline whose stages all pass is sufficient evidence that a change is safe to apply to production.

  4. Q4. The container registry is rate-limiting, Molecule cannot run, and an urgent security fix needs to merge. What is the designed response?

Passing score: 75%. Answers are checked in this browser.