Free — no signup required

Anatomy of a Makefile

2 min read

Rules, Targets, and the Tab Requirement

A Makefile is a plain text file named exactly Makefile (capital M, no extension) placed at the root of your project. It contains a collection of rules. Each rule has three parts:

target: dependencies
    command
  1. Target — The name of the task. This is what you type after make.
  2. Dependencies — Other targets that must complete successfully before this target runs. Leave blank if there are none.
  3. Command — The shell command to execute. This line must begin with a literal TAB character, not spaces. This is the single most common source of Makefile errors. Most editors can be configured to insert a tab when you press Tab inside a Makefile.

Dependency chaining is the payoff for that structure: a deploy target that lists build as its dependency causes Make to resolve the dependency graph automatically — it runs build first, and only proceeds to deploy if build exits with code 0. If build fails, the entire chain stops. This fail-fast behavior is exactly what you want in a deployment pipeline.

Variables

Makefiles support variables, which let you define a value once and reference it throughout the file. This is critical for avoiding repetition and making changes easy. Three assignment operators matter here:
- = — Simple assignment. The value is set when the Makefile is parsed.
- ?= — Conditional assignment. The variable is only set if it has not already been defined in the environment. This is ideal for defaults that CI/CD can override.
- := — Immediate assignment. The right-hand side is evaluated once at parse time, not lazily. Use this with $(shell ...) to avoid re-running the shell command on every reference.

The $(shell ...) function executes a shell command and substitutes its output — a common pattern is defaulting a version variable to the short Git commit hash this way, giving every build artifact a unique, traceable tag unless the caller overrides it.

The @ Prefix: Silencing Commands

By default, Make prints each command to the terminal before running it. This is useful for debugging but noisy in normal use. Prefixing a command with @ suppresses that echo, so only the command's own output appears, not the command line itself. Use @ on echo statements and routine commands; omit it when you want the raw command visible for transparency (such as terraform apply).

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free