Self-hosted service
golangci/golangci-lint avatar
golangci/golangci-lint

golangci-lint: a linters runner for Go teams that need one command and one config

Fast linters runner for Go

19,399 stars1,630 forksGoGPL-3.0

At a glance

What is it?
golangci-lint bundles over a hundred Go linters behind a single binary, a single YAML file and a single exit code. It is the right default for CI, and the wrong tool when you want one opinionated checker or a per-rule policy engine.
Who is it for?
Adopt golangci-lint if your Go repository already has more than one linter in CI, or if you want a single .golangci.yml and a single exit code instead of a shell script that chains tools. Skip it if you need one linter with a stable, versioned rule set you control yourself, or if you are linting generated code and cannot maintain an exclusion list.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What golangci-lint actually replaces in a Go repository

A Go project that wants more than go vet usually ends up with a shell script: staticcheck here, errcheck there, a custom grep for a banned import, each with its own exit code and its own output format. golangci-lint collapses that into one binary that runs the linters in parallel and reports their findings in one stream. The README describes it as a fast Go linters runner that "runs linters in parallel, uses caching, supports YAML configuration, integrates with all major IDEs, and includes over a hundred linters."

The audience is a team, not an individual. The value shows up when a finding has to block a pull request: one command, one configuration file committed next to the code, one non-zero exit status for CI to read. The go.mod file in the repository lists dependencies on dozens of upstream linter modules, which is the concrete form of that bundling: vet-style checks, error-handling checks, naming checks and style checks all arrive as one artifact. If you only ever wanted go vet, this is more machinery than you need.

How the runner, the config file and the cache fit together

The entry point is the cmd/golangci-lint directory, built by the Makefile into a binary named golangci-lint. Configuration is YAML, and the repository ships three reference files at the top level: .golangci.reference.yml, .golangci.next.reference.yml and .custom-gcl.reference.yml. Those names tell you something about the release process: there is a documented set of keys for the current line and a separate reference for the next one, so configuration keys move between versions and a config written against one release is not automatically valid against another.

Execution is parallel by design, and results are cached, which is why repeat runs on an unchanged tree are cheap. The repository also carries a jsonschema directory, which is how editors and CI validators can check a config file before it is used. The runner does not reimplement the rules; it orchestrates upstream linters and normalises their output, so a rule change in an upstream linter reaches you through a golangci-lint release rather than through your own edit.

Installing golangci-lint and running it once on a real module

The README points to two installation paths, one for a local machine and one for CI/CD systems, both hosted at golangci-lint.run. The repository also contains an install.sh script at the top level, and the project publishes a Docker image, which is the option referenced by the docker badge in the README. Pick the path that matches where the binary will run; a locally installed binary and a container image are two different upgrade stories.

Once the binary is on your PATH, the first useful command is a plain run from the module root. It uses the default linter set and prints findings for the current package tree:

bash
golangci-lint run

Expect a first run on an existing codebase to be noisy. That is the point of doing it before you wire anything into CI. The repository's own Makefile lints the project by building the binary and then invoking it with the run subcommand and the verbose flag:

bash
GL_TEST_RUN=1 ./golangci-lint run -v

The GL_TEST_RUN environment variable appears in the Makefile's test target, so it is a project-internal switch rather than a documented user flag. For your own repository, read the reference configuration rather than starting from memory. The repository's Makefile builds the binary from the cmd directory:

bash
go build -o golangci-lint ./cmd/golangci-lint

Treat the reference config as documentation to read, not as a config to commit as-is. The README does not document the migration steps between configuration generations; the repository keeps a dedicated migration target instead, which is a strong hint that copying a config across major versions is the normal failure mode.

Where golangci-lint is the wrong tool

The bundled model has a cost that the README does not discuss. Because the runner tracks upstream linters, the effective rule set is tied to the golangci-lint version you install, not to a rule set you own. Upgrading the binary can introduce new findings on unchanged code, and a CI job that pins the version will eventually be asked to move. The repository layout reflects this: CHANGELOG.md sits next to CHANGELOG-v1.md, and the reference configs are versioned separately from the main config used to lint the project itself.

There is also a structural limit. golangci-lint is a runner, so it can only enforce what its bundled linters can express. If your organisation needs a policy that is specific to your own domain, the custom linter route exists (the .custom-gcl.reference.yml file and the cmd directory are where that lives), but you are then maintaining a plugin, not a config line. And for a single-developer repository with one linter, the runner adds a configuration surface and a release cadence for no benefit. The project's own go.mod carries a note that only maintainers may change the minimum Go version, which is a reminder that the tool's supported toolchain moves on the maintainers' schedule.

golangci-lint against running staticcheck alone

The obvious alternative is to run one linter directly, and staticcheck is the usual candidate. The difference is not speed, it is governance. A single linter has one rule set, one changelog and one upgrade decision. golangci-lint has a rule set assembled from many upstream projects, exposed through a YAML file that lets you enable and disable individual linters and, through the reference config, tune their settings.

That trade is real in both directions. Running staticcheck alone means every finding you see comes from one project's judgement, and you can read its release notes in one sitting. Running golangci-lint means you get error-handling, naming and style checks in the same pass, with a single exit code, and you accept that the aggregate rule set changes when any of the bundled linters changes. Teams that already have a working single-linter setup and no appetite for a config file should not migrate just to have one command. Teams whose CI script has grown past three tools are the ones the runner is built for.

Maintenance, versions and the GPL-3.0 licence

The repository is not archived and the last push was on 2026-09-20, one day before this article's reference point, so the project is being worked on continuously. Releases are frequent and close together: v2.13.0 on 2026-08-19, v2.13.1 on 2026-08-20, v2.13.2 on 2026-08-27. That cadence is the upgrade cost in plain numbers. A team that pins the binary will be several patch releases behind within a month, and the migration target in the Makefile (clone_config, which runs the cloner under pkg/commands/internal/migrate) exists precisely because config files need moving between versions.

The licence is GPL-3.0, per the LICENSE file and the licence badge in the README. golangci-lint is a developer tool that you run, not a library you link into your product, so the common case is running the binary in CI or as a container. That said, the GPL question is about your distribution and your own build, not about how the tool feels to use, and the README does not address it. If you plan to redistribute the binary or bundle it into something you ship, read the LICENSE text and take your own advice on it; nothing in the repository documentation answers that for you.

Editorial conclusion

Adopt golangci-lint if your Go repository already has more than one linter in CI, or if you want a single .golangci.yml and a single exit code instead of a shell script that chains tools. Skip it if you need one linter with a stable, versioned rule set you control yourself, or if you are linting generated code and cannot maintain an exclusion list. Before rolling it out, pin a version such as v2.13.2, run it once with the default configuration to see the real finding count, and check the .golangci.reference.yml shipped in the repository for the keys your version supports.

Frequently asked questions

How do I install golangci-lint?

The README lists two documented paths, one for a local machine and one for CI/CD systems, both linked from golangci-lint.run. The repository also contains an install.sh script and the project publishes a Docker image, which the README references with a Docker badge.

How do I run golangci-lint on my project?

Run the golangci-lint binary from your module root; the README describes it as a linters runner, and the repository's own Makefile lints the project by invoking the built binary with the run subcommand. A first run on an existing codebase will report many findings, so expect to configure exclusions afterwards.

Can I use golangci-lint in VS Code?

The README states that the tool integrates with all major IDEs, and the repository ships a jsonschema directory so editors can validate the YAML configuration. The README does not give VS Code-specific setup steps; it points to golangci-lint.run for documentation.

What is golangci-lint and what does it do?

It is a linters runner for Go. The README says it runs linters in parallel, uses caching, supports YAML configuration, integrates with major IDEs and includes over a hundred linters, so a single command produces findings from many bundled tools.

How do I set up golangci-lint in a project?

The README points to golangci-lint.run for documentation, and the repository ships .golangci.reference.yml at the top level as the reference for configuration keys. The Makefile also shows the project linting itself with the run subcommand after building the binary.

Official sources

  1. golangci/golangci-lint on GitHub
  2. License: GPL-3.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/golangci-golangci-lint.svg)](https://hysenlabs.com/projects/golangci-golangci-lint)