yamllint
yamllint is a command-line linter for YAML files. It goes past the question of whether a file parses and checks the things a parser accepts happily but humans did not mean: duplicate keys, over-long lines, inconsistent indentation, trailing spaces, a missing newline at end of file, values like yes and on that YAML 1.1 quietly turns into booleans, and numbers that look octal. There are 23 rules, each with its own options and its own error, warning or disabled level, and you configure them by extending one of two shipped presets. Checks can be silenced for a line, a block or a whole file, and files can be excluded with gitignore-style patterns. It is written in Python on top of PyYAML and is packaged for most Linux distributions as well as PyPI.
The default YAML linter for good reason: nothing else covers duplicate keys, YAML 1.1 truthy traps and indentation with this level of per-rule control. Plan on writing a config before your first run, and pair it with a formatter, because yamllint will report problems forever and fix none of them.
Use it if
- You lint YAML in CI and want more than a parse check: duplicate keys and unquoted yes/no/on turning into booleans are the two YAML bugs that actually reach production
- You need per-rule control: every rule can be set to error, warning or disabled, given its own options, and given its own ignore patterns so one directory can have different standards
- You want integration without writing glue: -f parsable for editors, -f github for inline GitHub Actions annotations, an official pre-commit hook in the repository, and distro packages for dnf, apt and apk
- Your YAML is written and reviewed by people (Kubernetes manifests, Ansible playbooks, CI configs) and you would rather enforce a house style than argue about it in review
- You want files fixed, not reported: yamllint never writes to a file. If the goal is making 300 existing files consistent without hand-editing them, you need a formatter such as prettier or yamlfix and yamllint only afterwards
- Licensing matters in your product: yamllint is GPL-3.0. Running the CLI in a pipeline is uncontroversial, but importing yamllint.linter inside proprietary software is a copyleft question your legal team will want to answer, and most tools in this category are MIT
- Your real problem is invalid content rather than style: yamllint does not know what a Kubernetes manifest or a GitHub Actions workflow should contain, so misspelled keys and wrong value types pass clean. check-jsonschema or a schema-aware validator is the tool for that
- You need a green build today: the default preset caps lines at 80 characters, warns about every file missing a --- document start, and flags the on: key in every GitHub Actions workflow as truthy, so a first run on an existing repository produces hundreds of findings and an afternoon of config writing
- You are stuck on an old build image: 1.38 requires Python 3.10 or newer and made pathspec >= 1.0.0 a hard dependency, so older environments are pinned to an older yamllint
Setup reality
pip install yamllint is pure Python with two dependencies, pyyaml and pathspec >= 1.0.0, the latter promoted to a hard requirement in 1.38, which is the most likely conflict in a locked environment. Python 3.10 is the floor since 1.38 dropped 3.9. The first run on a real repository is loud rather than broken: line-length defaults to 80, document-start warns unless files begin with ---, comments must have two spaces before an inline # and a space after it, and truthy checks keys by default so every GitHub Actions on: line is flagged. Config discovery is the other thing that bites: with no -c flag yamllint looks for .yamllint, .yamllint.yaml or .yamllint.yml in the current directory and then walks up parent directories to your home directory, so a stray config two levels above your checkout can make CI and your laptop disagree. Exit codes are 1 for errors and 0 for warnings unless you pass -s/--strict, which turns warnings into exit code 2.
Patterns
Lint files or a whole treelint-files-and-directories
# one or more files
yamllint deploy.yaml .github/workflows/ci.yml
# recursively lint every YAML file under the current directory
yamllint .
# read from standard input
cat deploy.yaml | yamllint -Recursive mode only picks up paths matching the yaml-files setting, which defaults to *.yaml, *.yml and .yamllint, so a Kubernetes manifest named deploy.txt is skipped silently. Naming an explicit file always lints it regardless of extension.
Start from the relaxed preset or inline configuse-relaxed-preset
# built-in preset: fewer rules, most problems downgraded to warnings
yamllint -d relaxed file.yaml
# inline config as YAML source
yamllint -d "{extends: default, rules: {line-length: {max: 120}}}" .
# disable one rule for a single run
yamllint -d "{extends: default, rules: {document-start: disable}}" .If the -d value contains no colon, yamllint treats it as a preset name and expands it to extends: <name>, which is why -d relaxed works. -c and -d are mutually exclusive.
Write a .yamllint for the repositoryproject-config-file
# .yamllint
---
extends: default
rules:
line-length:
max: 120
level: warning
document-start: disable
indentation:
spaces: 2
indent-sequences: consistent
truthy:
check-keys: false # stop flagging GitHub Actions "on:"
comments:
min-spaces-from-content: 1Without -c, yamllint searches the current directory then every parent up to your home directory for .yamllint, .yamllint.yaml or .yamllint.yml, so a forgotten config above your checkout can change results. Rule dicts merge with the base preset rather than replacing it.
Run in GitHub Actions with inline annotationsgithub-actions-annotations
- name: Lint YAML
run: |
pip install yamllint==1.38.0
yamllint -f github --strict .-f github emits ::error and ::warning workflow commands so problems appear on the diff. Pin the version, because a later release can add a rule that fails a previously green repository. Without --strict, warnings exit 0 and the job passes.
Decide what counts as a failurestrict-exit-codes
yamllint . # exit 1 on errors, 0 if only warnings
yamllint -s . # exit 2 when there are only warnings
yamllint --no-warnings . # print errors only, warnings hidden
yamllint --list-files . # show which files would be linted, then exit 0The three exit codes are 0 clean, 1 at least one error, 2 warnings only under --strict. --list-files is the fastest way to find out why a file you expected to be checked is being skipped.
Silence a rule for a line, a block or a filedisable-checks-inline
# yamllint disable-file
---
this: file is skipped entirely
---
some_long_value: aaaaaaaaaaaaaaaaaaaaaa # yamllint disable-line rule:line-length
# yamllint disable rule:colons rule:commas
- Lorem : ipsum
dolor : sit amet
# yamllint enabledisable-file only works if it is the very first line of the file. A bare # yamllint disable turns off every rule until the matching # yamllint enable, so always name the rules you mean.
Exclude generated files and vendored treesignore-paths
# .yamllint
---
extends: default
ignore: |
/vendor/
/charts/**/templates/
*.generated.yaml
!/charts/values.yaml
# or reuse the patterns you already maintain
# ignore-from-file: [.gitignore, .yamlignore]Patterns follow gitignore semantics, including ! for re-inclusion. ignore and ignore-from-file cannot both be present in one config; yamllint raises a config error rather than merging them.
Relax one rule for some paths onlyper-rule-ignore
# .yamllint
---
extends: default
rules:
key-duplicates:
ignore: |
/fixtures/
*.template.yaml
line-length:
max: 80
ignore: |
/docs/Each rule takes its own ignore block, which is how you keep strict defaults while letting Helm templates and test fixtures through. A path excluded at the top level is skipped entirely and per-rule ignores never see it.
Wire it into pre-commitpre-commit-hook
# .pre-commit-config.yaml
---
repos:
- repo: https://github.com/adrienverge/yamllint
rev: v1.38.0
hooks:
- id: yamllint
args: [--strict, -c, .yamllint]The hook declares types: [file, yaml], so it runs on files pre-commit identifies as YAML rather than on your yaml-files config. Bumping rev can introduce new findings, so treat hook updates like a dependency upgrade.
Turn on the rules that ship disabledenable-optional-rules
# .yamllint
---
extends: default
rules:
quoted-strings:
quote-type: single
required: only-when-needed
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
empty-values:
forbid-in-block-mappings: true
key-ordering: enablequoted-strings, octal-values, empty-values, float-values, key-ordering and document-end are all disabled in the default preset because they are style choices, not bugs. octal-values is the exception worth enabling: an unquoted file mode like 0644 parses as decimal 644 under YAML 1.2.
Call the linter from Pythonpython-api
from yamllint import linter
from yamllint.config import YamlLintConfig
conf = YamlLintConfig('extends: default')
# or: conf = YamlLintConfig(file='.yamllint')
with open('deploy.yaml') as f:
for problem in linter.run(f, conf, filepath='deploy.yaml'):
print(problem.line, problem.column, problem.level, problem.desc,
problem.rule)linter.run returns a generator of LintProblem objects and accepts a string, bytes or a stream. Passing filepath is what makes the ignore patterns apply. Remember this import puts GPL-3.0 code inside your process.
The findings that are actual bugs, not stylecatch-real-yaml-bugs
---
# key-duplicates (error): the second value silently wins
port: 8080
port: 9090
# truthy (warning): parses as boolean True, not the string "yes"
enabled: yes
# octal-values (disabled by default): 644 decimal, not 0o644
mode: 0644These three are the reason to run yamllint at all. key-duplicates is an error in the default preset, truthy is only a warning so it passes CI unless you raise its level, and octal-values has to be enabled by hand.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| prettier | npm | You want YAML reformatted automatically instead of reported, and you already run a Node toolchain. |
| yamlfix | PyPI | You want a Python autoformatter for YAML you can chain before yamllint in the same pre-commit config. |
| check-jsonschema | PyPI | The failures you care about are wrong keys and wrong types, validated against schemas for GitHub Actions, GitLab CI, Kubernetes and others. |
| ruamel.yaml | PyPI | You need to read, modify and write YAML from Python with comments and formatting preserved rather than lint it. |