Introduction

ansible-lint is the standard tool for checking Ansible playbooks, roles, and collections against best practices. It catches syntax errors, deprecated features, security issues, and style violations before they reach production. This guide covers installation, configuration, CI/CD integration, and every essential feature.

Installation

# pip (recommended)
pip install ansible-lint

# With specific version
pip install ansible-lint==24.7.0

# Verify installation
ansible-lint --version

System Packages

# Fedora/RHEL
sudo dnf install ansible-lint

# Ubuntu/Debian
sudo apt install ansible-lint

# macOS
brew install ansible-lint

Basic Usage

Lint a Single Playbook

ansible-lint playbook.yml

Lint Entire Project

# Lint all playbooks and roles in current directory
ansible-lint

List All Available Rules

ansible-lint -L

Show Rule Details

ansible-lint -L | grep yaml
ansible-lint --show-relax-rules

Configuration File

Create .ansible-lint in your project root:

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

# Exclude paths from linting
exclude_paths:
  - .github/
  - tests/fixtures/
  - molecule/

# Skip specific rules
skip_list:
  - yaml[line-length]
  - no-changed-when

# Treat warnings as errors
strict: true

# Enable auto-fix
fix: false

# Offline mode (no network access)
offline: true

# Set working directory
# project_dir: .

Profiles

Profiles bundle rules by strictness level:

ProfileDescriptionUse Case
minBare minimum rulesLegacy projects
basicCore syntax and logicGetting started
moderate+ Style and namingActive development
safety+ Security rulesSecurity-conscious teams
shared+ Collaboration rulesShared codebases
productionAll rulesProduction automation
# Start lenient, tighten over time
profile: moderate

Key Rules

Syntax and Logic

RuleDescription
syntax-checkPlaybook fails ansible-playbook --syntax-check
parser-errorYAML parsing failures
no-changed-whenCommands/shell without changed_when
no-handlerTasks that should use handlers
no-jinja-whenJinja2 used inside when (already templated)

Style and Naming

RuleDescription
name[play]All plays should be named
name[task]All tasks should be named
name[casing]Task names should start with uppercase
yaml[line-length]Lines exceeding 160 characters
yaml[truthy]Use true/false instead of yes/no

Security

RuleDescription
no-log-passwordTasks with passwords should use no_log
partial-becomeInconsistent privilege escalation
risky-file-permissionsMissing file permissions on create

Deprecations

RuleDescription
deprecated-command-syntaxFree-form shorthand in command modules
deprecated-moduleUsing deprecated modules
latest[git] / latest[hg]Unpinned VCS checkouts

Autofix

ansible-lint can automatically fix some issues:

# Dry run — show what would be fixed
ansible-lint --fix --diff

# Actually fix files
ansible-lint --fix

Autofixable rules include:

  • yaml[truthy] — yes→true, no→false
  • yaml[octal-values] — Unquoted octals
  • name[casing] — Task name capitalization
  • fqcn — Add fully qualified collection names
  • key-order — Reorder task keys

Skipping Rules

Per-Task

- name: Run legacy script
  ansible.builtin.command: ./old-script.sh  # noqa: no-changed-when

Per-Play/Block

- name: Legacy play
  hosts: all
  vars:
    noqa: [risky-file-permissions]
  tasks: ...

In Configuration

# .ansible-lint
skip_list:
  - yaml[line-length]
  - no-changed-when
  - name[casing]

Warn Instead of Error

# .ansible-lint
warn_list:
  - yaml[line-length]
  - no-changed-when

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@main

GitLab CI

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

Pre-commit Hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/ansible/ansible-lint
    rev: v24.7.0
    hooks:
      - id: ansible-lint
pip install pre-commit
pre-commit install

Output Formats

# Default rich output
ansible-lint

# SARIF (for GitHub Code Scanning)
ansible-lint -f sarif > results.sarif

# JSON
ansible-lint -f json

# Codeclimate (GitLab)
ansible-lint -f codeclimate

# Plain text (for scripts)
ansible-lint -f pep8

Common Workflow

# 1. Check current state
ansible-lint --profile production

# 2. Auto-fix what's possible
ansible-lint --fix

# 3. Review remaining issues
ansible-lint

# 4. Skip rules you consciously accept
# Add to .ansible-lint or use inline noqa

Troubleshooting

"Couldn't resolve module/action"

Install the required collection:

ansible-galaxy collection install community.general

Too Many yaml[line-length] Warnings

# .ansible-lint
skip_list:
  - yaml[line-length]

Slow on Large Projects

# .ansible-lint
exclude_paths:
  - .git/
  - collections/
  - tests/output/

Conclusion

ansible-lint is essential for any serious Ansible project. Start with the moderate profile and tighten to production as your codebase matures. Use autofix for quick wins, integrate into CI/CD to catch issues early, and use .ansible-lint configuration to tune rules for your project. The combination of ansible-lint, pre-commit hooks, and CI/CD ensures consistent, high-quality Ansible code across your team.