<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Task-Runner on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/tags/task-runner/</link><description>Recent content in Task-Runner on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 03 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/tags/task-runner/index.xml" rel="self" type="application/rss+xml"/><item><title>Make — Task Runner Pattern</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/make/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/make/</guid><description>&lt;p&gt;Make predates most of the tooling in this notes collection by decades. Originally built to manage C compilation — track which source files changed, recompile only what&amp;rsquo;s needed. The dependency graph and incremental execution model are genuinely elegant.&lt;/p&gt;
&lt;p&gt;In modern use, Make is rarely used for what it was designed for. It is used as a &lt;strong&gt;task runner&lt;/strong&gt;: a consistent interface that wraps whatever the project actually needs to do.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-makefile" data-lang="makefile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;.PHONY&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; build test lint deploy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;build&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	docker build -t myapp .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;test&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	docker run --rm myapp pytest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;lint&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	docker run --rm myapp ruff check .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;deploy&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	helm upgrade --install myapp ./chart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;make build&lt;/code&gt;, &lt;code&gt;make test&lt;/code&gt;, &lt;code&gt;make lint&lt;/code&gt; — same interface regardless of what&amp;rsquo;s underneath. New team member, CI pipeline, colleague&amp;rsquo;s machine: one place to look.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="why-it-still-works"&gt;Why it still works
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Installed everywhere. No setup, no version to pin.&lt;/li&gt;
&lt;li&gt;Self-documenting entry point — &lt;code&gt;cat Makefile&lt;/code&gt; tells you how to work with the project.&lt;/li&gt;
&lt;li&gt;Trivially wraps Docker, shell scripts, language-specific tools, cloud CLIs.&lt;/li&gt;
&lt;li&gt;Low overhead — it is just shell execution with a thin layer on top.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fact that it is old is not a weakness. It is stable, understood, and available.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-phony-problem"&gt;The &lt;code&gt;.PHONY&lt;/code&gt; problem
&lt;/h2&gt;&lt;p&gt;Make&amp;rsquo;s native model assumes targets are files. &lt;code&gt;make build&lt;/code&gt; checks if a file named &lt;code&gt;build&lt;/code&gt; exists and skips the recipe if it does. &lt;code&gt;.PHONY&lt;/code&gt; declares that a target is not a file, forcing it to always run.&lt;/p&gt;
&lt;p&gt;Forgetting &lt;code&gt;.PHONY&lt;/code&gt; is a classic Make footgun. Always declare task targets phony.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-makefile" data-lang="makefile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;.PHONY&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; build test lint clean
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="modern-alternatives"&gt;Modern alternatives
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Just&lt;/strong&gt; (&lt;code&gt;Justfile&lt;/code&gt;) — purpose-built task runner, not a build system. Cleaner syntax than Make, no file-dependency model to work around, better error messages. Cross-platform. Requires installation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Task&lt;/strong&gt; (&lt;code&gt;Taskfile.yml&lt;/code&gt;) — YAML-based, similar goals to Just. More readable for people uncomfortable with Make syntax.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why still use Make then&lt;/strong&gt;: zero install cost, ubiquity in CI environments, and for many projects the &lt;code&gt;.PHONY&lt;/code&gt; quirk is the only real friction. If the team already knows Make and the project isn&amp;rsquo;t complex, switching to Just adds a dependency without solving a real problem.&lt;/p&gt;
&lt;p&gt;If starting fresh on a project where the team is comfortable with the tooling choice, Just is the cleaner option.&lt;/p&gt;
&lt;p&gt;It is there, it works. That is not faint praise — ubiquity and stability are real value. Every project gets a Makefile. New person joins, CI pipeline runs, colleague needs to build something: &lt;code&gt;make&lt;/code&gt; is the answer and it requires no explanation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="pattern-shared-interface-over-varied-internals"&gt;Pattern: shared interface over varied internals
&lt;/h2&gt;&lt;p&gt;The real value is not Make specifically — it is the pattern of having one consistent entry point regardless of what&amp;rsquo;s underneath. Whether that is Make, Just, or a &lt;code&gt;scripts/&lt;/code&gt; directory with a &lt;code&gt;run&lt;/code&gt; script, the goal is the same: anyone (person or pipeline) can run the project without knowing its internals.&lt;/p&gt;
&lt;p&gt;See also &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/thinking/shared-tooling-images/" &gt;Shared Tooling Images&lt;/a&gt; — the same principle applied to the tools themselves.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="where-this-pattern-applies"&gt;Where this pattern applies
&lt;/h2&gt;&lt;p&gt;The signal that logic is leaking into the wrong place: when a tool&amp;rsquo;s config format starts looking like a scripting language. That is the point to extract it into a script and wrap in Make.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build systems&lt;/strong&gt;
Gradle compiles, tests, and packages. Deployment logic, Docker builds, release steps → scripts. &lt;code&gt;make build&lt;/code&gt; calls Gradle. &lt;code&gt;make docker&lt;/code&gt; calls a script. &lt;code&gt;make deploy&lt;/code&gt; calls Helm. See &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/build-systems/" &gt;Build Systems&lt;/a&gt; for the full pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Terraform&lt;/strong&gt;: &lt;code&gt;terraform apply&lt;/code&gt; does the infra. Environment selection, var file injection, state backend config, plan review → scripts. &lt;code&gt;make plan&lt;/code&gt;, &lt;code&gt;make apply&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Helm&lt;/strong&gt;: Helm packages and deploys. Cluster selection, secret fetching, values overrides → scripts. &lt;code&gt;make deploy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ansible&lt;/strong&gt;: Ansible provisions. Inventory management, vault decryption, pre-flight checks → scripts. &lt;code&gt;make provision&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;
Test runners run tests. Environment setup, test data seeding, coverage reporting → scripts. &lt;code&gt;make test&lt;/code&gt; is the entry point regardless of whether it is pytest, jest, or go test underneath.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Package managers&lt;/strong&gt;
A common npm/yarn trap: stuffing 20 scripts into &lt;code&gt;package.json&lt;/code&gt; until it becomes a second build system. Keep &lt;code&gt;package.json&lt;/code&gt; for dependency management. Orchestration belongs in Make.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static site / Hugo&lt;/strong&gt;
Hugo builds the site. PDF fetching, Docker wrapping, deploy steps → Makefile. The pattern this site already uses.&lt;/p&gt;</description></item></channel></rss>