Makefile
Quick reference for Makefile patterns and commands
Using make as Project Task Runner
make is a build automation tool that executes targets defined in a Makefile. Common use: standardize project commands across repositories.
make # Run default target
make dev # Run specific target
Mark targets as .PHONY when they don’t produce files:
.PHONY: dev
dev:
uv run python main.py
Common Makefile Targets for Development Workflows
.PHONY: dev
dev:
uv run uvicorn main:app --reload
.PHONY: run
run:
uv run uvicorn main:app
.PHONY: build
build:
uv run python -m build
.PHONY: test
test:
uv run pytest -v
.PHONY: lint
lint:
uv run ruff check .
.PHONY: fmt
fmt:
uv run ruff format .
.PHONY: check
check: lint test
.PHONY: clean
clean:
rm -rf dist/ .venv/ __pycache__/
Using Variables and Environment in Makefile
APP_NAME := app
ENV ?= dev
.PHONY: deploy
deploy:
ENV=$(ENV) ./deploy.sh
.PHONY: aws
aws:
aws s3 ls --profile ${AWS_PROFILE}
echo "Current dir: $$PWD"
${VAR}- resolved bymakeat parse time$$VAR- passed to shell, resolved at execution time
Override variables from command line:
make deploy ENV=prod
Pass shell environment variables to make:
export AWS_PROFILE=production
make deploy
Understanding Shell Execution in Makefile
$(shell ...) executes at parse time when make reads the file, not during target execution:
# Runs immediately when Makefile is parsed
VERSION ?= $(shell git describe --tags --always --dirty)
This means all $(shell ...) calls complete before any target executes.
Conditionals and Assertions in Makefile
Conditionals:
ENV ?= dev
ifeq ($(ENV),prod)
DOCKER_TAG := latest
else
DOCKER_TAG := dev
endif
Assertions:
.PHONY: deploy
deploy:
ifndef AWS_PROFILE
$(error AWS_PROFILE is not set)
endif
./deploy.sh
Both are evaluated at parse time, not during target execution.
Calling Another Makefile from Makefile
Use $(MAKE) -C to run targets in subdirectories:
.PHONY: build
build:
$(MAKE) -C api build
$(MAKE) -C web build
.PHONY: test
test:
$(MAKE) -C api test
$(MAKE) -C web test
$(MAKE) ensures the same make binary is used. -C dir changes to directory before reading Makefile.
Building and Pushing Docker Images with Makefile
DOCKER := docker
DOCKERFILE := Dockerfile
DOCKER_REGISTRY := docker.io
DOCKER_IMAGE := $(DOCKER_REGISTRY)/username/app
DOCKER_TAG ?= $(shell git describe --tags --always --dirty)
DOCKER_PLATFORM ?= linux/amd64
.PHONY: build
build: ## Build the docker image
$(DOCKER) build --platform=$(DOCKER_PLATFORM) -f $(DOCKERFILE) -t $(DOCKER_IMAGE):$(DOCKER_TAG) .
.PHONY: push
push: build ## Push the docker image
$(DOCKER) tag $(DOCKER_IMAGE):$(DOCKER_TAG) $(DOCKER_IMAGE):latest
$(DOCKER) push $(DOCKER_IMAGE):$(DOCKER_TAG)
$(DOCKER) push $(DOCKER_IMAGE):latest
Self-Documenting Makefile with Help Target
Add ## description after target name, parse with awk:
.DEFAULT_GOAL := help
.PHONY: help
help: ## Show this help
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
Running make or make help outputs formatted list of targets with descriptions.