Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

Ansible / Config Mgmt

A CI Pipeline for Ansible: Lint, Syntax Check and Gate

An Ansible CI pipeline runs yamllint, ansible-lint and ansible-playbook --syntax-check on every pull request, then optionally tests roles and runs check mode against staging. It catches broken YAML, deprecated modules and risky patterns before anyone merges.

Key takeaways

  • Linting is cheap, fast and catches a surprising share of real defects before review.
  • ansible-lint profiles let you raise the bar gradually instead of failing on day one.
  • Syntax check proves the playbook parses; it does not prove it works, so it is only the first gate.
  • Pin the linter and collection versions, or the pipeline will fail for reasons unrelated to your change.
  • Production deployment should run from a reviewed, merged commit, never a feature branch.

Why Ansible needs a pipeline of its own

Application code goes through CI as a matter of course. Automation code, which can reconfigure every server you own in one command, often goes through nothing. A missing colon in a YAML file is found at the worst moment: halfway through a rolling run on production. A deprecated module name works until the next ansible-core upgrade, and then fails everywhere at once.

A lint and syntax pipeline is the cheapest insurance available. It takes a day to set up properly and prevents a class of avoidable incidents. It is also the base layer for everything else on our Ansible service: testing, approvals and platform deployment all assume that what reaches the main branch is at least well formed.

What good looks like

  • Every pull request runs the same checks, in the same pinned environment, in a few minutes.
  • A failing check blocks the merge. There is no “I will fix it later” path.
  • The ruleset is written down in the repository, with each exception explained.
  • The pipeline fails for your mistakes, not for random upstream changes.
  • Secrets cannot be committed unnoticed.

How it works in practice

The checks, cheapest first

  1. yamllint. Formatting and structure. Catches tab characters, duplicate keys and trailing spaces.
  2. ansible-lint. Ansible-specific rules, such as using fully qualified collection names and naming every task.
  3. Syntax check. ansible-playbook --syntax-check on each entry point playbook.
  4. Secret scanning. A tool such as gitleaks over the diff.
  5. Role tests. Molecule scenarios for roles that changed (see Molecule testing).
  6. Optional check mode. A --check --diff run against staging for playbooks, as a reviewer aid.

A rules file with intent

# .ansible-lint
profile: moderate
exclude_paths:
  - .github/
  - molecule/
warn_list:
  - experimental
skip_list:
  - yaml[line-length]   # long URLs in templates, reviewed 2026-10

Profiles are cumulative. We usually start at basic or moderate for an inherited codebase, fix what is easy, record the rest, and tighten towards production, which is the strictest and is intended for content you plan to share or publish. Every entry in skip_list carries a comment explaining why, so exceptions stay deliberate.

A GitHub Actions workflow

name: ansible-ci
on:
  pull_request:
  push:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Install pinned tooling
        run: pip install -r requirements-ci.txt
      - name: Install collections
        run: ansible-galaxy collection install -r collections/requirements.yml
      - name: yamllint
        run: yamllint .
      - name: ansible-lint
        run: ansible-lint
      - name: Syntax check
        run: |
          for pb in playbooks/*.yml; do
            ansible-playbook -i inventories/ci/hosts.yml --syntax-check "$pb"
          done

The same steps translate directly to GitLab CI, Azure Pipelines or Jenkins. If you are also modernising the pipelines around them, our CI/CD pipeline service and the guide to pipeline best practices cover the wider design. Pin the contents of requirements-ci.txt, for example ansible-core and ansible-lint to exact versions, and update them deliberately through a tool such as Renovate.

A pre-commit hook

Developers should see failures before pushing. A pre-commit configuration that runs yamllint and ansible-lint locally gives the fast feedback loop and keeps CI as the enforcement layer rather than the first place anyone learns of a problem.

Decisions and trade-offs

Strict from day one or gradual? On a new repository, start strict. On an inherited one, gradual is the only realistic path. Failing 400 findings on the first run teaches people to ignore the tool.

Auto-fix or report only? ansible-lint --fix can rewrite some findings, such as module names. We run it in a branch, review the diff and commit, rather than letting CI modify code.

Check mode on production? We avoid it in CI. Credentials with production reach should not sit in pull request pipelines. Staging check mode is a safer compromise.

When this is not enough. Linting proves style and some correctness; it does not prove behaviour. A role that installs the wrong package version will lint perfectly. That is the job of tests and staging runs.

Common failure modes

  • Unpinned tooling. A new linter release adds a rule and every pipeline fails overnight. Pin versions.
  • Missing collections in CI. Syntax checks fail because a collection is not installed. Install from a requirements file.
  • Skips without reasons. The skip list grows silently. We require a comment and review it quarterly.
  • Pipelines that can deploy from any branch. Deployment credentials are limited to the protected main branch.
  • Slow pipelines. If CI takes 25 minutes, people bypass it. We cache dependencies and run Molecule only for changed roles.

Delivery and handover

You get the workflow files, the lint configuration, pre-commit hooks, a short document describing the checks and how to add a rule, and branch protection settings. If you deploy via AWX, we connect the merge to a project sync so that what is reviewed is what runs. Secret-handling checks tie in with the Vault practices we set up.

A worked example: introducing the linter to a noisy repository

Teams with years of existing playbooks rarely start clean, and switching the linter on at full strictness produces hundreds of findings that everyone learns to ignore. The approach that works is to ratchet. First, run the linter in report mode and record the current findings as a baseline. Second, fail the build only on findings in files changed by the pull request, so new work is clean while old work is untouched. Third, fix legacy findings in small batches, one rule at a time, starting with the ones that indicate real risk such as missing become handling or shell tasks that should be modules.

Every few weeks, move one rule from warning to error. This keeps the profile getting stricter without a single painful migration week. Skips belong in a configuration file with a comment explaining why, never as inline noqa tags scattered through roles, because a central list can be reviewed and an inline tag cannot.

Finally, pin the linter version in the pipeline and upgrade it deliberately. A new release often adds rules, and an unpinned version means yesterday’s green build is red for reasons nobody changed. Treat the upgrade as its own pull request with its own findings list, so the team sees exactly what the new version objects to and can decide what to fix now and what to schedule.

How to tell it is working

  • Pull requests fail early and clearly, and the message tells the author how to fix it.
  • Findings per pull request trend downward, and the profile gets stricter over time.
  • Nobody has pushed a syntax error to production in months.
  • Linter upgrades are a planned change, not a surprise.

To get a baseline lint report on your current repository, contact us.

References

How we deliver it

  1. 01

    Baseline the repository

    We run ansible-lint as is, record the findings and agree which to fix now and which to defer explicitly.

  2. 02

    Configure the rules

    A shared .ansible-lint file sets the profile, excluded paths and any justified skips, each with a comment.

  3. 03

    Build the pipeline

    Pull requests run yamllint, ansible-lint, syntax check and secret scanning on your CI platform of choice.

  4. 04

    Add role tests

    Changed roles trigger Molecule runs, covered in more detail on the testing page.

  5. 05

    Gate the merge

    Branch protection requires a green pipeline and review, and nobody pushes straight to the main branch.

  6. 06

    Connect to deployment

    Merges to main can trigger a check mode run against staging or sync a project in AWX.

Tools and technologies

ansible-lintyamllintansible-playbookMoleculeGitHub ActionsGitLab CIAzure Pipelinespre-commitgitleaksAWXRenovate

Frequently asked questions

What does ansible-lint check?
It checks playbooks, roles and collections against rules covering YAML formatting, deprecated or non-FQCN module names, risky shell usage, missing task names, unsafe file permissions and common idempotency problems. It also supports profiles that group rules by strictness, and you can add custom rules.
Is ansible-playbook --syntax-check enough?
No. It confirms a playbook parses and that referenced roles and imports resolve, but it does not run tasks or detect logic errors, wrong variables or missing packages. Treat it as the cheapest gate, and add linting, role tests and a check mode run against staging for real confidence.
What are ansible-lint profiles?
Profiles are named rule sets from min through basic, moderate, safety, shared and production, each stricter than the last. They let a team start on a lenient profile and move upward as the codebase improves, instead of facing hundreds of failures on the first run of a strict set.
Can we run Ansible check mode in CI?
Yes, against a staging environment, using the --check and --diff flags. It simulates changes without applying them, but some modules cannot predict their effects, so results are indicative rather than a guarantee. It is valuable for catching accidental large diffs before a merge.
Which CI platform should we use?
The one you already run. The checks are the same command line tools on GitHub Actions, GitLab CI, Azure Pipelines or Jenkins. Using your existing platform means existing permissions, runners and audit apply, and nobody has to learn a second system.

Talk to an engineer about ansible lint ci pipeline

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.