Every project needs a make dev, make test, make deploy. Makefiles give you that with zero dependencies.
Why Makefile
- Zero install — make is preinstalled on macOS and Linux
-
Self-documenting —
make helplists all available commands - Language-agnostic — works with Python, Node, Go, Rust, anything
- Composable — chain commands with dependencies
The Template
.PHONY: help dev test lint build deploy clean
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
dev: ## Start development server
python -m uvicorn app:main --reload --port 8000
test: ## Run tests
python -m pytest tests/ -v --tb=short
lint: ## Run linter
ruff check . --fix
ruff format .
build: ## Build Docker image
docker build -t myapp .
deploy: build ## Deploy (builds first)
docker push myapp:latest
ssh prod "docker pull myapp:latest && docker compose up -d"
clean: ## Remove build artifacts
rm -rf __pycache__ .pytest_cache dist/ build/ *.egg-info
find . -name "*.pyc" -delete
How to Use
make help # List all commands
make dev # Start development
make test # Run tests
make deploy # Build + deploy
Key Concepts
.PHONY
.PHONY: test
Tells make that test is a command, not a file. Without this, if you have a file called test, make will skip the command.
Dependencies
deploy: build test # Runs build and test BEFORE deploy
Variables
PYTHON := python3
PORT := 8000
dev:
$(PYTHON) -m uvicorn app:main --port $(PORT)
Override at runtime: make dev PORT=3000
Conditional Logic
ENV ?= development # Default value, override with ENV=production make deploy
deploy:
ifeq ($(ENV),production)
@echo "Deploying to production..."
kubectl apply -f k8s/production/
else
@echo "Deploying to staging..."
kubectl apply -f k8s/staging/
endif
Real Examples
Python Project
.PHONY: install dev test lint format
install:
pip install -e ".[dev]"
dev:
uvicorn app:main --reload
test:
pytest -v --cov=app
lint:
ruff check .
format:
ruff format .
Node.js Project
.PHONY: install dev test build
install:
npm ci
dev:
npm run dev
test:
npm test
build:
npm run build
deploy: build
npx vercel --prod
Data Pipeline
.PHONY: scrape process analyze report
scrape:
python scrapers/run_all.py
process: scrape
python pipeline/clean.py
python pipeline/transform.py
analyze: process
python analysis/generate_insights.py
report: analyze
python reports/weekly.py
@echo "Report generated at reports/output/"
Run make report and it executes the entire pipeline in order.
Pro Tips
-
Add
helpas default target — put it first in the file -
Use
@to suppress echo —@echo "quiet"instead of seeing the command twice -
Use
$(MAKE)for recursive calls — not justmake - Keep it simple — if your Makefile is over 50 lines, reconsider
📧 spinov001@gmail.com — I build automated data pipelines and developer tools.
Related: 10 Dev Tools I Use Daily | 5 GitHub Actions Workflows | 150+ Free APIs
Top comments (0)