Make was written to compile C, and I haven't used it to compile C in years. I still put a Makefile at the root of nearly every project, Python, Go, a static site, a pile of shell scripts, because it has quietly become the one thing I can rely on to answer the question every collaborator and every future-me actually asks: how do I run this thing?
The pitch is small and unglamorous. make is already installed on every machine I touch. Everyone has typed make something before. And a target is just a name in front of a command, so it documents the project's verbs in the same file that runs them. You clone a repo, you see the Makefile, and within ten seconds you know it has a test, a lint, a run and a deploy. That's the whole value, and it's a lot of value.
A minimal one for a Python service looks like this, and it's deliberately boring:
.PHONY: install test lint run
install:
pip install -r requirements.txt
test:
pytest -q
lint:
ruff check .
run:
python -m myapp
Two things in there matter more than they look. The .PHONY line tells Make these targets aren't files to be checked for freshness, they're just commands to run. Forget it and the day someone creates a directory called test, your make test silently stops doing anything, and you'll waste a genuinely embarrassing amount of time working out why. The other is the tab. Make demands a real tab character to indent recipes, not spaces, and an editor that helpfully converts them will hand you the most cryptic error message in the toolchain. Configure your editor to leave tabs alone in Makefiles and never think about it again.
Beyond that, a couple of habits keep it from rotting. I add a help target that greps the file and prints the available commands, so make on its own is self-documenting instead of running whatever the first target happens to be. I keep recipes to one or two lines; the moment a target wants real logic, branching, loops, error handling, I move that into a proper script and have Make call it. Make is a menu, not a programming language, and treating it as the latter is how you end up with a Makefile nobody dares touch.
There are flashier task runners. Plenty of them are better at their specific niche. But they're one more thing to install, one more syntax to learn, and one more dependency that might not be there on the box you've just SSHed into at an awkward hour. A Makefile is the lowest common denominator that's actually pleasant to use, and the lowest common denominator is exactly what you want for the front door of a project. It just has to be there, and it has to work, the way Make does. C optional.