ansible-lint
ansible-lint checks playbooks, roles, and collections for practices and behavior that could be improved. It encodes community best practices as rules organized into tiered profiles, supports automatic fixing of common issues, and integrates with the Language Server for real-time diagnostics in your editor.
The tool is opinionated by design — following the same philosophy as black and prettier — promoting content that reads like documentation, is production-ready, and provides consistent results.
Installation
Section titled “Installation”# Via the full suite (recommended)pip install ansible-dev-tools
# Standalonepip install ansible-lint
# With locked dependencies (Python 3.10+, in venv)pip install "ansible-lint[lock]"
# Fedora/RHELdnf install ansible-lintVerify installation:
ansible-lint --versionCore concepts
Section titled “Core concepts”Profiles
Section titled “Profiles”ansible-lint uses a tiered profile system where each level includes all rules from the level below. Choose a profile based on your content’s intended audience:
| Profile | Purpose | Use when |
|---|---|---|
min |
Content is parseable | You just need syntax validation |
basic |
Standard style and formatting | Writing internal playbooks |
moderate |
Maintainability best practices | Team-shared content |
safety |
Security and determinism | Production deployments |
shared |
Galaxy/Automation Hub quality | Publishing to Galaxy |
production |
AAP certified/validated standards | Enterprise content |
Set a profile in your config file or via CLI:
ansible-lint --profile moderateansible-lint ships with 45+ rules, each with a unique string ID. Rules check for:
- Style: naming conventions, YAML formatting, key ordering
- Best practices: FQCN usage, handler patterns, idempotency
- Security: file permissions, password logging, prompts
- Compatibility: deprecated modules, bare variables, free-form syntax
- Metadata: galaxy.yml completeness, changelog presence, runtime.yml
Rules can have sub-rules (e.g., fqcn[action], fqcn[canonical], name[casing], name[template]).
Inline suppression
Section titled “Inline suppression”Suppress a rule on a specific line with # noqa:
- name: This task needs special permissions ansible.builtin.file: path: /etc/app.conf mode: "0777" # noqa: risky-file-permissionsSuppress multiple rules:
- name: Legacy task # noqa: fqcn[action] no-changed-when shell: /opt/legacy.shConfiguration
Section titled “Configuration”Config file locations
Section titled “Config file locations”ansible-lint searches for configuration in this order (within the project and parent directories, but not outside the git repo):
.ansible-lint.ansible-lint.yml.ansible-lint.yaml.config/ansible-lint.yml.config/ansible-lint.yaml
Configuration reference
Section titled “Configuration reference”profile: moderate
# Paths to exclude from lintingexclude_paths: - .github/ - changelogs/ - molecule/
# Rules to skip entirelyskip_list: - yaml[line-length] - no-changed-when
# Rules to report as warnings (non-blocking)warn_list: - experimental - role-name[path]
# Opt-in rules to activateenable_list: - fqcn - no-log-password
# Rules allowed to auto-fix (default: all)write_list: - all
# Disable network accessoffline: false
# Mock unavailable modules during syntax checkmock_modules: - custom_internal_module
# Mock unavailable roles during syntax checkmock_roles: - custom_internal_role
# Variable naming pattern (regex)var_naming_pattern: "^[a-z_][a-z0-9_]*$"
# Loop variable prefix pattern (regex)loop_var_prefix: "^(__|{role}_)"
# Task name prefix for name[prefix] ruletask_name_prefix: "{stem} | "
# Maximum nested block depthmax_block_depth: 20
# Custom rule directoriesrulesdir: - ./custom_rules/
# Extra variables for syntax checkextra_vars: foo: bar
# SARIF output file (for CI integration)sarif_file: ansible-lint-results.sarifIgnore file
Section titled “Ignore file”For gradual adoption, create .ansible-lint-ignore to suppress known violations:
# Format: filename rule-id [# comment]playbooks/legacy.yml fqcn[action]roles/old_role/tasks/main.yml no-changed-when # TODO: fix laterAuto-generate from current violations:
ansible-lint --generate-ignoreProfiles in detail
Section titled “Profiles in detail”Ensures content is loadable by Ansible:
internal-error— ansible-lint internal failuresload-failure— files that cannot be loadedparser-error— YAML/Jinja parsing failuressyntax-check—ansible-playbook --syntax-checkfailures
Standard formatting and style (includes all min rules):
command-instead-of-module— use modules instead of shell commandscommand-instead-of-shell— usecommandwhen shell features aren’t neededdeprecated-bare-vars/deprecated-module/deprecated-local-action— outdated syntaxjinja— Jinja2 spacing and syntaxkey-order— consistent key ordering in tasksname— task naming conventions (casing, templates, prefixes)no-free-form— avoid free-form module argumentsno-jinja-when— don’t wrapwhenvalues in{{ }}partial-become— incomplete privilege escalationrole-name— role naming conventionsschema— validate against Ansible JSON schemasvar-naming— variable naming conventionsyaml— YAML formatting (via yamllint)
moderate
Section titled “moderate”Maintainability and quality (includes all basic rules):
galaxy— galaxy.yml metadata completenessignore-errors— avoid blanketignore_errors: truemeta-incorrect/meta-no-tags— role metadata issuesno-changed-when— tasks that change state must declarechanged_whenno-handler— use handlers instead ofwhen: result.changedno-relative-paths— avoid../in pathsmax-block-depth/max-tasks— complexity limits
safety
Section titled “safety”Security and determinism (includes all moderate rules):
latest— avoidlatestas a version/tagno-log-password— sensitive data must useno_logno-prompting— avoid interactive promptspackage-latest— avoidstate: latestfor packagesrisky-file-permissions— files must specify explicit permissionsrisky-octal— catch malformed octal permissionsrisky-shell-pipe— pipes must handle non-zero exit codes
shared
Section titled “shared”Galaxy/Automation Hub publishing quality (includes all safety rules):
galaxy[no-changelog]— changelog requiredgalaxy[tags]— tags required in galaxy.ymlgalaxy[version-incorrect]— valid semver versionmeta-runtime— runtime.yml requirementsno-tabs— no tab characters
production
Section titled “production”AAP certified/validated content standards (includes all shared rules):
avoid-dot-notation— no dot notation for dict accessfqcn— fully qualified collection names required everywhereimport-task-no-when— nowhenonimport_tasksmeta-no-dependencies— no collection dependencies in certified contentsanity— passesansible-test sanitysingle-entry-point— one role entry pointuse-loop— preferloopoverwith_*
Autofix
Section titled “Autofix”ansible-lint can automatically fix many common issues:
# Fix all fixable issuesansible-lint --fix
# Fix only specific rulesansible-lint --fix yaml,key-order,jinjaRules with autofix support:
| Rule | What it fixes |
|---|---|
command-instead-of-shell |
Converts shell to command when safe |
deprecated-local-action |
Converts to delegate_to: localhost |
fqcn |
Adds fully qualified collection names |
jinja |
Fixes Jinja2 spacing |
key-order |
Reorders task keys to standard order |
name |
Fixes task name casing |
no-free-form |
Converts free-form to structured arguments |
no-jinja-when |
Removes {{ }} wrapping in when |
no-log-password |
Adds no_log: true to password tasks |
partial-become |
Completes partial become blocks |
yaml |
Reformats YAML (always runs with --fix) |
YAML reformatting always runs when --fix is used, regardless of write_list or skip_list settings.
CLI reference
Section titled “CLI reference”# Lint specific filesansible-lint playbook.yml roles/myrole/
# Lint with a specific profileansible-lint --profile safety
# List all available rulesansible-lint -L
# List available profilesansible-lint -P
# Output in SARIF format (for CI)ansible-lint -f sarif --sarif-file results.sarif
# Output in Code Climate formatansible-lint -f codeclimate
# Skip specific rulesansible-lint -x yaml[line-length],no-changed-when
# Run only specific tagsansible-lint -t security
# Strict mode (non-zero exit on warnings)ansible-lint -s
# Offline mode (no network)ansible-lint --offline
# Verbose outputansible-lint -vvvOutput formats:
| Format | Flag | Use case |
|---|---|---|
full |
-f full |
Detailed human-readable output (default) |
brief |
-f brief |
Compact output |
sarif |
-f sarif |
SARIF JSON for CI integration |
json |
-f json |
Alias for SARIF |
codeclimate |
-f codeclimate |
GitLab Code Quality integration |
pep8 |
-f pep8 |
Compact one-line-per-finding |
quiet |
-f quiet |
Minimal output |
md |
-f md |
Markdown formatted |
YAML formatting
Section titled “YAML formatting”ansible-lint uses the yamllint library internally with these defaults:
- Line length: max 160 characters
- Comments: 1 space minimum from content
- Braces: max 1 space inside
- Octal values: forbids both implicit and explicit
- Truthy: consistent boolean formatting
If you have a .yamllint file, it takes precedence — but it must remain compatible with the ansible-lint reformatter.
Editor integration
Section titled “Editor integration”Language Server (LSP)
Section titled “Language Server (LSP)”ansible-lint integrates with editors via the Ansible Language Server:
- VS Code: Official
redhat.ansibleextension - Vim/Neovim:
coc-ansible - Emacs:
lsp-modewith Ansible client
The Language Server runs ansible-lint in the background on file open and save, presenting findings as editor diagnostics (errors and warnings).
VS Code settings
Section titled “VS Code settings”| Setting | Default | Description |
|---|---|---|
ansible.validation.enabled |
true |
Enable/disable validation |
ansible.validation.lint.enabled |
true |
Enable/disable ansible-lint |
ansible.validation.lint.path |
"ansible-lint" |
Path to ansible-lint executable |
ansible.validation.lint.arguments |
"" |
Extra CLI arguments |
ansible.validation.lint.autoFixOnSave |
false |
Run --fix on save |
How it works
Section titled “How it works”- When you open or save an Ansible file, the Language Server invokes ansible-lint
- Findings appear as VS Code errors (rule violations) or warnings (rules in
warn_list) - Quick fixes are available for rules that support autofix
- If ansible-lint is unavailable, the server falls back to
ansible-playbook --syntax-check
CI/CD integration
Section titled “CI/CD integration”GitHub Actions
Section titled “GitHub Actions”- name: Run ansible-lint uses: ansible/ansible-lint@main with: args: "--profile moderate" working_directory: ./ansible/Pre-commit hook
Section titled “Pre-commit hook”repos: - repo: https://github.com/ansible/ansible-lint rev: v26.4.0 hooks: - id: ansible-lintSARIF output for CI
Section titled “SARIF output for CI”Generate machine-readable results alongside human output:
ansible-lint --sarif-file results.sarifThis writes SARIF to the file while still showing normal output on stdout — useful for uploading to GitHub Code Scanning, SonarQube, or other SARIF consumers.
Environment variables
Section titled “Environment variables”| Variable | Purpose |
|---|---|
ANSIBLE_LINT_NODEPS |
Skip dependency installation and module-dependent checks |
Custom rules
Section titled “Custom rules”Write custom rules by extending AnsibleLintRule:
from ansiblelint.rules import AnsibleLintRule
class MyCustomRule(AnsibleLintRule): id = "custom-001" description = "Ensure all tasks use company namespace" severity = "MEDIUM" tags = ["custom", "company"]
def matchtask(self, task, file): module = task.get("action", {}).get("__ansible_module__", "") if module and not module.startswith("company."): return "Task uses non-company module: %s" % modulePlace custom rules in a directory and reference it:
rulesdir: - ./custom_rules/Custom rules with autofix support can implement TransformMixin:
from ansiblelint.rules import AnsibleLintRule, TransformMixin
class MyFixableRule(AnsibleLintRule, TransformMixin): id = "custom-002" # ...
def transform(self, match, lintable, data): # Modify data in place to fix the issue passNext steps
Section titled “Next steps”Your content passes linting. Now prove it works:
- Test with Molecule — verify your roles and playbooks against real infrastructure
- Run playbooks with ansible-navigator — execute and debug interactively or in containers
- CLI Reference — complete rule catalog, configuration options, profiles, and CLI flags
- PyPI: ansible-lint
- GitHub: ansible/ansible-lint