<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Make on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/tags/make/</link><description>Recent content in Make on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 04 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/tags/make/index.xml" rel="self" type="application/rss+xml"/><item><title>These Are Not the Pipelines You Are Looking For</title><link>https://backend-engineering-strategy-tools.github.io/site/thinking/one-command-any-pipeline/</link><pubDate>Thu, 04 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/thinking/one-command-any-pipeline/</guid><description>&lt;p&gt;There are a lot of &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/platforms/" &gt;CI/CD platforms&lt;/a&gt;. GitHub Actions, Tekton, Jenkins, Jenkins X, Harness, Bitbucket Pipelines, GitLab CI, CircleCI, Argo Workflows. The choice between them is a perennial argument — and mostly the wrong one.&lt;/p&gt;
&lt;p&gt;The mistake is putting your build logic inside the pipeline. Once you do that, the pipeline owns your build. Reproducing a failure locally means setting up the CI environment. Switching platforms means rewriting all the steps. Testing the pipeline means pushing a commit and waiting. The platform becomes load-bearing.&lt;/p&gt;
&lt;p&gt;The fix is straightforward: keep all logic in &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/make/" &gt;Make&lt;/a&gt; or &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/dagger/" &gt;Dagger&lt;/a&gt;. The pipeline calls one command. &lt;code&gt;make build&lt;/code&gt;. &lt;code&gt;make test&lt;/code&gt;. &lt;code&gt;dagger call publish&lt;/code&gt;. That&amp;rsquo;s it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The pipeline shrinks to thin orchestration: trigger on a git event, check out the code, call the command, report the result. It does not know what the build does. It does not care. You can swap GitHub Actions for Tekton or Tekton for Argo Workflows and the only thing that changes is the YAML wrapper around the same command.&lt;/p&gt;
&lt;p&gt;More importantly: you can run the same command on your laptop. No CI environment to replicate, no &amp;ldquo;it works locally but fails in CI&amp;rdquo; gap to debug. The build is the build, wherever it runs.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This matters more the longer a project lives. Build tools consolidate and diverge. Platforms get acquired, deprecated, or repriced. Jenkins pipelines from five years ago are maintained by people who weren&amp;rsquo;t there when they were written and can&amp;rsquo;t run them outside of Jenkins. The projects that survive these transitions cleanly are the ones where the build logic was never in the pipeline to begin with — it was in a Makefile or a build tool that could run anywhere.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/build-systems/" &gt;Gradle&lt;/a&gt;, Maven, Make — these predate CI/CD platforms by decades and will outlast the current generation of them. Put your logic there. The pipeline is a trigger and a reporter. Keep it that way.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The pattern in practice:&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;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 myimage:&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;VERSION&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; .
&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; go test ./...
&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;publish&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 push myimage:&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;VERSION&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# GitHub Actions — the entire pipeline&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;make build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;make test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;make publish&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Tekton — same command, different wrapper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;build-test-publish&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;golang:1.22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;script&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; make build
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; make test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; make publish&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Same logic. One command. Any pipeline.&lt;/p&gt;</description></item><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>