Makefile Patterns: Task Running That Works Everywhere
Make isn't just for C projects. It's a universal task runner that works on every Unix system without installing anything.
February 24, 2026 · 8 min · 1544 words · Rob Washington
Table of Contents
Make has been around since 1976. It’s installed on virtually every Unix system. And while it was designed for compiling C programs, it’s become a universal task runner for any project.
.PHONY:helphelp:## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$'$(MAKEFILE_LIST)|\
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'.PHONY:buildbuild:## Build the application
go build -o bin/app ./cmd/app
.PHONY:testtest:## Run tests
go test -v ./...
.PHONY:lintlint:## Run linter
golangci-lint run
.PHONY:cleanclean:## Clean build artifacts
rm -rf bin/
Now make help automatically lists all targets with descriptions:
defineHELP_TEXTUsage:make [target]
Targets: build Build the app
test Run tests
deploy Deploy to production
endefexportHELP_TEXThelp: @echo "$$HELP_TEXT"
.PHONY:allbuildtestdeployall:buildtest## Build and test
build:## Build application
go build -o bin/app
test:build## Run tests (builds first)
go test ./...
deploy:test## Deploy (tests first)
./scripts/deploy.sh
.PHONY:setupsetup:## Set up development environment
@echo "Installing dependencies..." go mod download
pip install -r requirements.txt
npm install
@echo "Creating directories..." mkdir -p bin logs tmp
@echo "Done! Run 'make build' to build."
NODE_MODULES:= node_modules
NPM:= npm
.PHONY:installbuildtestlintdevclean$(NODE_MODULES):package.json$(NPM) install
@touch $(NODE_MODULES)install:$(NODE_MODULES)## Install dependencies
build:install## Build for production
$(NPM) run build
test:install## Run tests
$(NPM)testlint:install## Run linter
$(NPM) run lint
dev:install## Start development server
$(NPM) run dev
clean:## Clean up
rm -rf $(NODE_MODULES) dist .cache
.PHONY:cicdci:linttestbuild## Run CI pipeline
@echo "CI passed!"cd:ci## Run CD pipeline
@if ["$(CI)" !="true"];then\
echo"CD should only run in CI environment";\
exit 1;\
fi$(MAKE) docker-push
$(MAKE) deploy
# Different behavior based on OS
ifeq($(shelluname),Darwin)OPEN:= open
elseOPEN:= xdg-open
endifdocs-open:docs## Open docs in browser
$(OPEN) docs/index.html
# Check for required tools
check-deps: @command -v docker >/dev/null ||(echo"docker required"&&exit 1) @command -v kubectl >/dev/null ||(echo"kubectl required"&&exit 1)
Tabs, not spaces — Make requires tabs for indentation
Shell per line — Each line runs in a new shell; use \ for continuation
.PHONY matters — Without it, targets can conflict with filenames
Variables expand late — Use := for immediate evaluation
Make is old, quirky, and has a learning curve. But once you know it, you have a task runner that works everywhere, requires no installation, and does exactly what you tell it. That’s worth the initial investment.
📬 Get the Newsletter
Weekly insights on DevOps, automation, and CLI mastery. No spam, unsubscribe anytime.