Introduction

ansible-lint checks playbooks, roles, and task files for errors, anti-patterns, and best practice violations. It catches problems before you run anything — missing names, deprecated syntax, command modules used instead of proper modules, and more.

Install

pip install ansible-lint

# Verify
ansible-lint --version

Basic Usage

# Lint a playbook
ansible-lint playbook.yml

# Lint all playbooks in directory
ansible-lint playbooks/

# Lint a role
ansible-lint roles/myapp/

# Auto-fix issues
ansible-lint --fix playbook.yml

# Show all rules
ansible-lint -L

# Show specific rule details
ansible-lint -R yaml[truthy]

Common Rules

RuleDescription
name[missing]Task is missing a name
fqcn[action-core]Use FQCN for builtin modules
yaml[truthy]Use true/false not yes/no
no-changed-whenCommand/shell missing changed_when
command-instead-of-moduleUse module instead of command
risky-shell-pipeShell pipe without pipefail
no-jinja-whenDon't use {{ }} in when
var-namingVariable name doesn't match pattern
role-nameRole name doesn't match pattern
schemaYAML doesn't match Ansible schema

Fix Common Issues

name[missing] — Add Task Names

# BAD
- apt: { name: nginx }

# GOOD
- name: Install nginx
  ansible.builtin.apt:
    name: nginx

fqcn — Use Fully Qualified Names

# BAD
- apt:
    name: nginx

# GOOD
- ansible.builtin.apt:
    name: nginx

# BAD
- docker_container:
    name: myapp

# GOOD
- community.docker.docker_container:
    name: myapp

yaml[truthy] — Use true/false

# BAD
become: yes
enabled: no

# GOOD
become: true
enabled: false

no-changed-when — Mark Command Tasks

# BAD — lint warning
- name: Check version
  ansible.builtin.command: /opt/app/version
  register: ver

# GOOD
- name: Check version
  ansible.builtin.command: /opt/app/version
  register: ver
  changed_when: false

# GOOD — custom change detection
- name: Apply migration
  ansible.builtin.command: /opt/app/migrate
  register: result
  changed_when: "'Applied' in result.stdout"

command-instead-of-module

# BAD — use apt module
- ansible.builtin.command: apt-get install -y nginx

# GOOD
- ansible.builtin.apt:
    name: nginx
    state: present

# BAD — use file module
- ansible.builtin.command: mkdir -p /opt/myapp

# GOOD
- ansible.builtin.file:
    path: /opt/myapp
    state: directory

no-jinja-when

# BAD — don't use {{ }} in when
- service: { name: nginx, state: started }
  when: "{{ install_nginx }}"

# GOOD
- service: { name: nginx, state: started }
  when: install_nginx

Configuration

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

# Skip specific rules
skip_list:
  - yaml[line-length]
  - name[template]

# Warn instead of fail
warn_list:
  - experimental

# Exclude paths
exclude_paths:
  - .cache/
  - .github/
  - molecule/
  - tests/

# Extra rules directories
# rulesdir:
#   - ./custom-rules/

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

# Enable specific optional checks
enable_list:
  - no-same-owner

Profiles

ProfileStrictnessBest For
nullNo rulesDisabled
minMinimalGetting started
basicStandardMost projects
moderateStricterShared code
safetySecurity focusSecurity-critical
sharedTeam standardPublished roles
productionStrictestProduction code

Skip Rules Inline

# Skip specific rule for one task
- name: Run custom script  # noqa: command-instead-of-module
  ansible.builtin.command: /opt/custom-script.sh
  changed_when: false

# Skip multiple rules
- name: Risky but necessary  # noqa: no-changed-when risky-shell-pipe
  ansible.builtin.shell: cat /tmp/data | grep pattern

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
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install ansible-lint
      - run: ansible-lint

Pre-Commit Hook

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

Auto-Fix

# Fix what can be auto-fixed
ansible-lint --fix

# Fixes:
# - FQCN (adds ansible.builtin. prefix)
# - yaml[truthy] (yes → true)
# - Some formatting issues

Troubleshooting

Too Many Warnings

Start with a permissive profile and tighten over time:

# .ansible-lint — start here
profile: basic
skip_list:
  - yaml[line-length]
  - name[template]

"Couldn't resolve module"

Install required collections:

ansible-galaxy collection install -r requirements.yml

Conclusion

ansible-lint catches problems before they hit production. Use it in CI/CD to enforce quality. Start with the basic profile and work toward production. The most impactful rules: use FQCN (fqcn), name every task (name[missing]), use changed_when on command tasks, and use modules instead of raw commands. Run ansible-lint --fix to auto-fix common issues. Integrate with pre-commit hooks for instant feedback.