Click any item to expand the explanation and examples.
📐 Basics
Targets and recipes basics
# Makefiletarget: dependencies
recipe (MUST be a tab, not spaces)
build: npm run build
test: npm test
Run with: make build, make test
First target is the default
make (with no args) runs the first target
.PHONY — non-file targets basics
# Without .PHONY, if a file named "build" exists, # make thinks the target is up-to-date and skips itAlways declare.PHONY: build test clean deploy
build: npm run build
test: npm test
clean: rm -rf dist node_modules
deploy: ./deploy.sh
.PHONY for targets that don’t produce files.
Dependencies basics
# deploy depends on test, which depends on build deploy: test ./deploy.shtest: build npm test
build: npm run build
make deploy → runs build, then test, then deploy
📝 Variables
Variables and assignment vars
# Simple assignment APP_NAME = myapp PORT = 3000Use with $(VAR)
run: docker run -p $(PORT):$(PORT) $(APP_NAME)
Override from command line
make run PORT=8080
Environment variable (export to subprocesses)
export NODE_ENV = production
Conditional (set only if not already set)
PORT ?= 3000
Shell command output
GIT_SHA := $(shell git rev-parse —short HEAD) DATE := $(shell date +%Y-%m-%d)
build: docker build -t $(APP_NAME):$(GIT_SHA) .
= is lazy (evaluated when used). := is immediate (evaluated when defined). Use := for shell commands.
⚡ Common Patterns
Project automation Makefile pattern
.PHONY: help install dev build test lint clean deployTheDefault target — show help
help: @echo “Available targets:” @echo ” install — Install dependencies” @echo ” dev — Start dev server” @echo ” build — Build for production” @echo ” test — Run tests” @echo ” lint — Run linter” @echo ” clean — Remove build artifacts” @echo ” deploy — Deploy to production”
install: npm ci
dev: npm run dev
build: npm run build
test: npm test
lint: npx eslint src/
clean: rm -rf dist node_modules .next
deploy: build npx vercel —prod
@ prefix suppresses printing the command itself (only shows output).
Docker project Makefile pattern
APP_NAME := myapp TAG := $(shell git rev-parse --short HEAD).PHONY: build run stop logs shell
build: docker build -t $(APP_NAME):$(TAG) .
run: docker compose up -d
stop: docker compose down
logs: docker compose logs -f
shell: docker compose exec app bash
clean: docker compose down -v docker image prune -f
Multi-line commands and conditionals advanced
# Each line runs in a separate shell. Use \ for multi-line. setup: @echo "Setting up..." && \ npm install && \ cp .env.example .env && \ echo "Done!"Conditionals
deploy: ifeq ($(ENV),production) @echo “Deploying to production…” ./deploy-prod.sh else @echo “Deploying to staging…” ./deploy-staging.sh endif
Ignore errors with -
clean: -rm -rf dist -docker compose down