pre-commit
Framework for managing Git pre-commit hooks
pre-commit is an open-source framework for managing and maintaining multi-language Git pre-commit hooks. It runs locally and in self-hosted CI to enforce code quality before commits land.
Key features
- Multi-language hooks
- Auto-installs tools
- Large hook ecosystem
- CI integration
Pros & cons
Strengths
- Multi-language hook support
- Huge hook library
- Simple YAML config
Trade-offs
- Slow first run
- Python required
pre-commit replaces
Last reviewed Aug 26, 2026 · 758 words
There is nothing to host. pre-commit is a Python command-line tool and one YAML file in the root of a repository; the payoff for a self-hoster is that the same file stops secrets, broken YAML and unformatted code from ever reaching your Gitea or Forgejo server, and then runs again inside CI so the check cannot be skipped with a flag. It has 15,537 GitHub stars and has been doing this since 2014, which in developer tooling is a lifetime.
One YAML file, any language, no shared tool installs
Each hook lives in its own Git repository, pinned to a tag, and pre-commit builds an isolated environment for it on first use: a virtualenv for Python hooks, a node_modules for JavaScript hooks, a Go build for Go hooks. Nobody on the team installs the linter by hand, and everyone runs the identical version. A starting config for a homelab repo full of compose files and shell scripts:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
args: ["--maxkb=500"]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.4
hooks:
- id: gitleaks
Install with pipx install pre-commit, run pre-commit install once per clone, and pre-commit autoupdate bumps every rev to the latest tag when you want it to. The check-added-large-files hook alone has saved me from committing a 400 MB database dump more than once.
The first run is slow; every run after is cached
Building those environments takes anywhere from 20 seconds to a few minutes, which is the "slow first run" complaint you will read everywhere. It happens once per machine per hook version; everything is cached under ~/.cache/pre-commit and subsequent commits add well under a second for the hooks above. Two things make it feel slow forever: adding hooks that run a full test suite (do not; hooks should finish in seconds), and changing rev values weekly so the cache never settles.
Run the same config in Gitea Actions or Woodpecker
The hosted pre-commit.ci service is convenient, but a self-hosted forge can do the same job with one step:
- run: pipx install pre-commit && pre-commit run --all-files
Cache ~/.cache/pre-commit between runs, keyed on the config file's hash, and the CI step drops from minutes to seconds. --all-files is the important flag: a local hook only sees staged files, so CI is where you catch the file someone committed with --no-verify. Gitea Actions, Woodpecker CI and Drone all take the step verbatim; the CI/CD category covers which runner suits a small server.
Hooks that pay for themselves in infrastructure repos
Beyond the basics: shellcheck and shfmt for the scripts that inevitably accumulate; hadolint for Dockerfiles; ansible-lint if you run playbooks; yamllint for compose files with tighter rules than check-yaml; and a language: system local hook when you want to call a tool already on the machine, such as docker compose config -q to validate a compose file without pulling anything. Keep the total under 10 hooks. Each one you add is one more thing a new contributor sees fail on their first commit.
When lefthook or plain git hooks are better
pre-commit requires Python on every developer machine, which is a non-issue on Linux and macOS and a small irritation on Windows. If your team is JavaScript-only, Husky is already in the ecosystem, and lefthook is a single Go binary with parallel execution and no runtime dependency, which matters on a 20-repo monorepo where pre-commit's sequential hooks start to drag. For a single infrastructure repo maintained by one or two people, none of that outweighs pre-commit's hook library, which is several hundred maintained hooks against a few dozen for the alternatives.
What I'd do
Add the config above to every repo that holds compose files, playbooks or scripts, run pre-commit run --all-files once to clean the history's accumulated mess in a single commit, and put the same command in CI on your forge. Leave autoupdate as a monthly manual task, not an automated PR, so a hook version bump never lands unread. Total cost: one afternoon, one 15-line file, and never again pushing a private key to your own Git server.
Compare pre-commit
1 head-to-head comparisons.
Similar developer tools & git apps
Excalidraw
Developer Tools & GitVirtual hand-drawn style whiteboard
Replaces Miro
lazygit
Developer Tools & GitSimple terminal UI for Git commands
Replaces GitKraken, Sourcetree
Hoppscotch
Developer Tools & GitOpen-source API development ecosystem
Replaces Postman, Insomnia
json-server
Developer Tools & GitFull fake REST API from a JSON file in seconds
Replaces Mockoon, Postman Mock
Strapi
Developer Tools & GitLeading open-source headless CMS
Replaces Contentful
NocoDB
Developer Tools & GitOpen-source Airtable alternative
Replaces Airtable