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 Lint: Analyze & Fix Playbooks for Best Practices (Complete Guide)

By Luca Berton · Published 2024-01-01 · Category: installation

How to use ansible-lint to analyze and fix Ansible playbooks. Detect errors, enforce best practices, custom rules, CI/CD integration.

What Is ansible-lint?

ansible-lint is the official linting tool for Ansible content. It checks playbooks, roles, and collections against a set of rules that enforce best practices, catch common mistakes, and improve code quality.

Think of it as ESLint for Ansible — it won't run your playbooks, but it will tell you what's wrong before you do.

See also: Ansible-Lint: Complete Guide to Linting Playbooks & Roles

Install

# pip (recommended)
pip install ansible-lint

# pipx (isolated) pipx install ansible-lint

# Verify ansible-lint --version # ansible-lint 24.12.2 using ansible-core 2.18.1

Basic Usage

# Lint a playbook
ansible-lint site.yml

# Lint all YAML files in current directory ansible-lint

# Lint a role ansible-lint roles/webserver/

# Lint with specific profile ansible-lint -p production

# Fix auto-fixable issues ansible-lint --fix

# Show all rules ansible-lint -L

# List rules by tag ansible-lint -T

See also: Ansible Lint: Check & Fix Playbooks for Best Practices (2026 Guide)

Understanding Rules

Rule Categories

| Tag | Description | Example | |-----|-------------|---------| | command-instead-of-module | Use modules instead of shell/command | Use ansible.builtin.apt not apt-get install | | deprecated | Deprecated syntax or features | with_itemsloop | | experimental | New rules being tested | Various | | formatting | YAML formatting issues | Indentation, trailing spaces | | idempotency | Non-idempotent tasks | Missing creates/removes on command | | metadata | Role/collection metadata issues | Missing meta/main.yml | | name | Task naming conventions | Missing or badly formatted names | | no-changed-when | Missing changed_when | Shell/command without change detection | | partial-become | Inconsistent privilege escalation | become on some tasks but not play | | risky | Potentially dangerous operations | ignore_errors: true | | schema | Schema validation failures | Invalid playbook structure | | syntax | YAML/Ansible syntax errors | Bad indentation, invalid keywords | | unpredictability | Non-deterministic behavior | git without version | | yaml | YAML best practices | Truthy values, line length |

Most Common Warnings and Fixes

name[missing] — Tasks should be named

# ❌ Bad
- ansible.builtin.apt:
    name: nginx
    state: present

# ✅ Good - name: Install nginx ansible.builtin.apt: name: nginx state: present

fqcn[action-core] — Use FQCN for modules

# ❌ Bad
- name: Install package
  apt:
    name: nginx

# ✅ Good - name: Install package ansible.builtin.apt: name: nginx

no-changed-when — Commands need change detection

# ❌ Bad
- name: Check disk space
  ansible.builtin.command: df -h

# ✅ Good - name: Check disk space ansible.builtin.command: df -h changed_when: false

# ✅ Also good (for commands that change things) - name: Run database migration ansible.builtin.command: python manage.py migrate changed_when: "'No migrations to apply' not in migration_result.stdout" register: migration_result

command-instead-of-module — Use modules

# ❌ Bad
- name: Install nginx
  ansible.builtin.shell: apt-get install -y nginx

# ✅ Good - name: Install nginx ansible.builtin.apt: name: nginx state: present

# ❌ Bad - name: Create directory ansible.builtin.command: mkdir -p /opt/app

# ✅ Good - name: Create directory ansible.builtin.file: path: /opt/app state: directory mode: '0755'

yaml[truthy] — Avoid truthy values

# ❌ Bad
become: yes
ignore_errors: yes

# ✅ Good become: true ignore_errors: true

risky-file-permissions — Set explicit permissions

# ❌ Bad (inherits umask)
- name: Create config file
  ansible.builtin.copy:
    content: "config data"
    dest: /etc/app/config.yml

# ✅ Good - name: Create config file ansible.builtin.copy: content: "config data" dest: /etc/app/config.yml mode: '0644'

no-free-form — Avoid free-form syntax

# ❌ Bad
- name: Check uptime
  ansible.builtin.command: uptime

# ✅ Good - name: Check uptime ansible.builtin.command: cmd: uptime

jinja[spacing] — Consistent Jinja2 spacing

# ❌ Bad
dest: "{{item.path}}"

# ✅ Good dest: "{{ item.path }}"

Configuration

.ansible-lint Configuration File

# .ansible-lint
---
# Profiles: min, basic, moderate, safety, shared, production
profile: moderate

# Exclude paths exclude_paths: - .github/ - molecule/ - tests/ - "*.md"

# Skip specific rules skip_list: - yaml[line-length] # Allow long lines - name[casing] # Allow any case in task names

# Warn instead of error warn_list: - no-changed-when - command-instead-of-module - experimental

# Enable optional rules enable_list: - args - empty-string-compare - no-log-password - no-same-owner

# Custom rules directory # rulesdir: # - ./custom_rules/

# Offline mode (don't download schemas) offline: false

# Use default rules use_default_rules: true

# Ansible configuration # parseable: true # quiet: true # strict: false

Profiles

Profiles are preset rule bundles, from permissive to strict:

| Profile | Rules | Best For | |---------|-------|----------| | min | Syntax only | Legacy codebases | | basic | min + essential rules | Getting started | | moderate | basic + style rules | Most teams | | safety | moderate + security rules | Security-conscious teams | | shared | safety + collaboration rules | Open source / shared content | | production | All rules | Enterprise / certified content |

# Use a specific profile
ansible-lint -p production site.yml

See also: Ansible-Lint Rule Analysis and Best Practices

Auto-Fix

ansible-lint can automatically fix many issues:

# Preview fixes (dry-run)
ansible-lint --fix=all --diff site.yml

# Apply fixes ansible-lint --fix site.yml

# Fix specific rule ansible-lint --fix=yaml[truthy] site.yml

Auto-fixable rules include: • yaml[truthy]yes/notrue/falseyaml[octal-values] — Octal file modes • fqcn[action-core] — Short module names → FQCNs • name[casing] — Task name capitalization • jinja[spacing] — Jinja2 template spacing • no-free-form — Free-form to structured arguments

CI/CD Integration

GitHub Actions

# .github/workflows/lint.yml
name: Ansible Lint
on: [push, pull_request]

jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4

- name: Run ansible-lint uses: ansible/ansible-lint@v24 # Or manually: # - run: pip install ansible-lint # - run: ansible-lint

GitLab CI

# .gitlab-ci.yml
ansible-lint:
  image: python:3.12
  stage: test
  before_script:
    - pip install ansible-lint
  script:
    - ansible-lint
  rules:
    - changes:
        - "**/*.yml"
        - "**/*.yaml"
        - roles/**/*

Pre-commit Hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/ansible/ansible-lint
    rev: v24.12.2
    hooks:
      - id: ansible-lint
        additional_dependencies:
          - ansible-core>=2.18
pip install pre-commit
pre-commit install
# Now ansible-lint runs automatically before each commit

Custom Rules

Create custom rules for your organization's standards:

# custom_rules/check_company_tags.py
"""Ensure all tasks have company-standard tags."""
from ansiblelint.rules import AnsibleLintRule

class CompanyTagsRule(AnsibleLintRule): id = "company-tags" shortdesc = "Tasks must have team and service tags" description = ( "All tasks in production playbooks must include " "'team' and 'service' tags for accountability." ) severity = "MEDIUM" tags = ["company", "metadata"]

def matchtask(self, task, file=None): if file and "production" in str(file.path): tags = task.get("tags", []) if not any(t.startswith("team:") for t in tags): return "Task missing 'team:' tag" if not any(t.startswith("service:") for t in tags): return "Task missing 'service:' tag" return False

# .ansible-lint
rulesdir:
  - ./custom_rules/

ansible-lint with VS Code

Install the Ansible extension for VS Code:

// .vscode/settings.json
{
  "ansible.validation.lint.enabled": true,
  "ansible.validation.lint.path": "ansible-lint",
  "ansible.validation.lint.arguments": "-p moderate",
  "ansible.ansible.useFullyQualifiedCollectionNames": true
}

This gives you real-time linting feedback as you write playbooks — red squiggles under issues, hover for explanations, quick-fix suggestions.

Integrating with Molecule

# molecule/default/molecule.yml
lint: |
  set -e
  ansible-lint

Or in newer Molecule versions, add to your CI pipeline to run lint before converge.

Common Workflow

# 1. Write playbook
vim roles/webserver/tasks/main.yml

# 2. Lint locally ansible-lint roles/webserver/

# 3. Auto-fix what you can ansible-lint --fix roles/webserver/

# 4. Fix remaining issues manually

# 5. Commit git add -A && git commit -m "feat: add webserver role"

# 6. CI runs ansible-lint again to verify

FAQ

What's the difference between ansible-lint and yamllint?

yamllint checks generic YAML syntax and formatting. ansible-lint checks Ansible-specific best practices — module usage, task naming, idempotency, deprecations, and security. ansible-lint includes yamllint rules via the yaml[] rule set, so you typically only need ansible-lint.

Should I use --fix in CI/CD?

No. Use --fix locally during development. In CI/CD, run ansible-lint without --fix — it should only report errors, not modify code. If it finds issues, the PR fails and the developer fixes them locally.

How do I ignore a specific rule for one task?

Add a # noqa comment:

- name: Run legacy script  # noqa: command-instead-of-module
  ansible.builtin.shell: /opt/legacy/run.sh

Or for all rules on a task: # noqa: all

Which profile should my team use?

Start with basic or moderate. Move to safety or production as your codebase matures. Avoid starting with production on a legacy codebase — you'll get hundreds of warnings and developers will just skip it.

Does ansible-lint slow down CI pipelines?

No. ansible-lint is fast — it typically completes in seconds even for large codebases. It only parses YAML files, it doesn't execute anything.

Conclusion

ansible-lint is essential for maintaining quality Ansible automation. Start with a moderate profile, integrate into your CI/CD pipeline and pre-commit hooks, use --fix for auto-fixable issues, and gradually increase strictness as your codebase improves. The key rules to enforce from day one: FQCN module names, task names, changed_when on commands, explicit file permissions, and true/false instead of yes/no.

Related Articles

Ansible Documentation Complete GuideAnsible Collection Role Testing with MoleculeAnsible CI/CD Pipeline IntegrationAnsible Best Practices: ignore_errorsInstall Ansible Complete GuideInstall ansible-lint on macOSAnsible-Lint Rule Analysis and Best PracticesExploring Ansible-Lint Profiles

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home