# MegaLinter: one CI step that runs every linter your repository touches

> OX Security's MegaLinter bundles dozens of linters into a single container or GitHub Action, with reporters that write SARIF and comments back into the platform. The project is a Dockerfile first and a Python package second, and it lints itself with its own configuration.

**oxsecurity/megalinter** — 🦙 MegaLinter analyzes 50 languages, 22 formats, 21 tooling formats, excessive copy-pastes, spelling mistakes and security issues in your repository sources with a GitHub Action, other CI tools or locally.

- Repository: https://github.com/oxsecurity/megalinter
- Website: http://megalinter.io/
- Stars: 2,611 · Forks: 312
- Language: Dockerfile
- License: AGPL-3.0
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/oxsecurity-megalinter

## A Dockerfile with a hundred pinned tool versions

GitHub reports MegaLinter's primary language as Dockerfile, which is the single most informative fact in the repository metadata. This is not a Python library that shells out to tools you have installed. It is an image, built from a `Dockerfile` that pins a version argument for every tool it carries, each annotated with a renovate datasource comment so the pin gets updated by automation rather than by hand.

The generated header in that file explains how it is produced: it says the Dockerfile is generated by `.automation/build.py` using descriptor files and should not be updated manually. So the build image is compiled output, and the source of truth for which linters ship is a set of descriptor files, not the Dockerfile you read.

A short excerpt shows the shape of a pin:

```bash
ARG CARGO_SARIF_FMT_VERSION=0.8.0
ARG ACTION_ACTIONLINT_VERSION=1.7.12
ARG BASH_SHELLCHECK_VERSION=v0.11.0
ARG CARGO_ZIZMOR_VERSION=1.25.0
```

Each line names a tool and a version, and each carries a renovate comment identifying whether the version came from a crate, a Docker tag or a GitHub release. That is the mechanism that makes a hundred-tool image maintainable, and it is also what you would have to reproduce if you ever needed to build your own flavour, which is what `Dockerfile-custom-flavor` in the tree exists for.

## A hundred linters, and the question of which ones run

The README's headline claim is a count: it supports 67 languages, 23 formats and 22 tooling formats, ready to use out of the box as a GitHub Action or with any CI system. Note that the repository description in GitHub's own metadata says 50 languages and 22 formats. The README gives the larger number and the description gives the smaller one, and both are current enough to be unremarkable: the count moves as linters are added, and the description field simply lags.

What matters for an adopter is that the default is broad rather than tuned. A tool that analyses everything will eventually surface findings in files nobody on the team owns, in a format the project has never linted before, generated by a tool version chosen by a renovate bot. That is not a reason to reject the project, but it is the operational fact that decides whether MegaLinter is a good fit.

The `.mega-linter.yml` file at the repository root is the configuration surface, and its existence in a repository that also has `.vale.ini`, `.cspell.json`, `.secretlintignore`, `.trivyignore`, `.trufflehogignore`, `.grype.yaml` and `.lycheeignore` is a good illustration of the scope. Spelling, secrets, dependency vulnerabilities, prose style, link checking and code linting are all in the same run.

## Reporters decide whether anyone reads the output

The integration icons under the README's introduction are a list of reporters and install paths: GitHub, GitLab, Azure, Bitbucket, Jenkins, Drone, Concourse and Docker, plus SARIF, Grafana and Datadog for observability. A linter run that produces a log nobody reads is close to worthless, so the reporter layer is where MegaLinter spends its design attention.

SARIF is the one that matters most for adoption, because it is the format GitHub code scanning consumes, which turns findings into annotations on the diff rather than a wall of text in the job log. The comment reporters push findings back into the pull request conversation, which is where a reviewer will actually see them.

This also determines what you can do with the results. A project already wired into Grafana or Datadog can treat lint findings as observability data with the same dashboards it uses for everything else, rather than as pass or fail noise. Whether that is worth the setup is a question about your existing infrastructure rather than about MegaLinter.

The repository tree supports the reading: `action.yml` is the GitHub Action entry point, `TEMPLATES/` holds CI templates, and `docs/` holds one page per reporter and per install path.

## A Python package under the image, and a licence to reconcile

There is a Python package underneath. `pyproject.toml` declares the name `megalinter`, built with hatchling, requiring Python 3.12 or newer, authored by Nicolas Vuillamy, and classifying the project as Development Status 4, Beta. Its dependencies are the orchestration layer: gitpython, jsonpickle, pygithub, python-gitlab, pyjwt, pytablewriter, pyyaml, redis and requests, with urllib3 pinned at 2.5.0 or newer.

There is also an LLM dimension that is easy to miss in an image full of deterministic linters. The optional dependency groups are named `llm`, `huggingface` and `all-llm`, and they pull in langchain integrations for OpenAI, Anthropic, Google GenAI, Mistral, Ollama, DeepSeek and the community package, plus langchain-huggingface with transformers and torch. So the tool that checks your code can also call a model to help interpret or fix it, which is a meaningful difference from a pure static analyser.

The licence is where the repository contradicts itself, and you should notice it. GitHub reports the repository licence as AGPL-3.0, and `LICENSE` in the tree is the AGPL text. But `pyproject.toml` sets `license = "MIT"` in the project metadata. Those are different grants. If you are triaging this, read `LICENSE` and treat the pyproject field as a stale packaging artefact rather than as an alternative grant, and check with the maintainers if your use is sensitive to the difference.

## Dogfooding: the repository runs its own linters on itself

The root of the tree is a demonstration of the project configured on a real repository. `Dockerfile`, `Dockerfile-custom-flavor`, `Dockerfile-quick`, `Dockerfile-release` and `Dockerfile-worker` are five entry points for different ways of running the image, and there is a `Makefile` whose targets are organised around a bootstrapped development environment.

```bash
make bootstrap
make clean
```

The `bootstrap` target chains three steps, `python-bootstrap`, `python-bootstrap-dev` and `nodejs-bootstrap`, and `clean` chains three teardown steps in turn, including a `nodejs-clean`. The file also computes its Python launcher from `.python-version` and includes make fragments found under `.config/make`. So even the developer workflow is multi-runtime, which tells you how much of the tool's own surface spans more than one language before it ever looks at your code.

The rest of the root continues the point: `.pre-commit-hooks.yaml` means the linters can run before a commit rather than only in CI, `.agents/`, `.claude/`, `.claude-plugin/`, `.codex-plugin/` and `.cursor-plugin/` are directories for coding agent integrations, and `.automation/` is the generator that builds the Dockerfile. A repository that keeps a plugin directory per agent tool is a project whose direction is clearly toward meeting developers where they already work.

## Where the alternative beats it

The comparison that matters is not against a single linter, because MegaLinter contains them all. It is against running those linters directly, and against a commercial platform that bundles similar checks with a dashboard.

Running the tools directly wins on speed and on failure isolation. If your repository is a single language with a single formatter, `npm run lint` or a pre-commit configuration finishes in seconds, tells you exactly what broke, and needs no container. MegaLinter's value appears when the count of languages and formats makes per-language CI configuration a chore nobody wants to own, or when you need one consistent set of findings across twenty repositories.

Commercial platforms such as SonarQube sit on the other side of the trade. They give you history, dashboards and per-developer feedback across many repositories, which MegaLinter does not attempt to be. The MegaLinter route is the self-hosted one: your configuration file in your repository, your CI minutes, your reports.

The real cost is compute. Every linter in the bundle needs to be downloaded or built inside the image, and a run over a large repository is slower than the sum of the linters you actually need. Turning linters off is supported, but it means the configuration file grows from the moment you adopt, which is the ongoing ownership cost the project does not advertise.

## Conclusion

MegaLinter fits a polyglot monorepo where nobody wants to hand-maintain a dozen per-language CI steps, and it does not fit a small single-language repository that already has a fast, working lint command. The number to check before adopting it is your own CI minute cost, because every linter in the bundle runs on every push unless you turn them off. GitHub reports the last push on 2026-09-24 with v10.1.0 published 2026-09-05, so read the changelog for that release before pinning.

## FAQ

### How many linters does MegaLinter include?

The README states support for 67 languages, 23 formats and 22 tooling formats, while GitHub's repository description still says 50 languages and 22 formats. The counts move as linters are added, so the README figure is the current one and the description field lags behind it. Each tool is pinned by a version argument in the generated Dockerfile, with renovate comments recording where each version came from.

### Do I have to run every linter MegaLinter ships?

The project is configured through a `.mega-linter.yml` file, and turning individual linters off is part of that configuration. A run with the full bundle is slower than the sum of the linters you need, so the configuration file tends to grow after adoption rather than stay as it shipped.

### Can MegaLinter report findings back into my pull requests?

Yes. The README lists comment reporters for GitHub, GitLab, Azure and Bitbucket, plus a SARIF reporter for GitHub code scanning, and observability integrations for Grafana and Datadog. `.pre-commit-hooks.yaml` in the repository root also means the same linters can run before a commit.

## Sources

- [License: AGPL-3.0](https://github.com/oxsecurity/megalinter/blob/main/LICENSE)
- [oxsecurity/megalinter on GitHub](https://github.com/oxsecurity/megalinter)
- [Project website](http://megalinter.io/)
- [README](https://github.com/oxsecurity/megalinter/blob/main/README.md)
- [Releases](https://github.com/oxsecurity/megalinter/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/oxsecurity-megalinter
