DEV Community

Alex Spinov
Alex Spinov

Posted on

Just Use Makefile — The Build Tool You Already Have

Every project needs a make dev, make test, make deploy. Makefiles give you that with zero dependencies.

Why Makefile

  1. Zero install — make is preinstalled on macOS and Linux
  2. Self-documentingmake help lists all available commands
  3. Language-agnostic — works with Python, Node, Go, Rust, anything
  4. 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
Enter fullscreen mode Exit fullscreen mode

How to Use

make help     # List all commands
make dev      # Start development
make test     # Run tests
make deploy   # Build + deploy
Enter fullscreen mode Exit fullscreen mode

Key Concepts

.PHONY

.PHONY: test
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Variables

PYTHON := python3
PORT := 8000

dev:
    $(PYTHON) -m uvicorn app:main --port $(PORT)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/"
Enter fullscreen mode Exit fullscreen mode

Run make report and it executes the entire pipeline in order.

Pro Tips

  1. Add help as default target — put it first in the file
  2. Use @ to suppress echo@echo "quiet" instead of seeing the command twice
  3. Use $(MAKE) for recursive calls — not just make
  4. 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)