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
| Rule | Description |
|---|---|
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-when | Command/shell missing changed_when |
command-instead-of-module | Use module instead of command |
risky-shell-pipe | Shell pipe without pipefail |
no-jinja-when | Don't use {{ }} in when |
var-naming | Variable name doesn't match pattern |
role-name | Role name doesn't match pattern |
schema | YAML 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
| Profile | Strictness | Best For |
|---|---|---|
null | No rules | Disabled |
min | Minimal | Getting started |
basic | Standard | Most projects |
moderate | Stricter | Shared code |
safety | Security focus | Security-critical |
shared | Team standard | Published roles |
production | Strictest | Production 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
Related Articles
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.