Generating Makefiles and Justfiles for Repeatable Ops Tasks
Use AI to turn ad-hoc shell commands into clean Makefile and justfile task runners your whole team can run safely, with guard prompts and back-out paths.
- #automation
- #make
- #just
- #tooling
I keep a scratchpad of shell commands the way some people keep loose cables in a drawer: I know one of them does the thing I need, but I have to paw through five before I find it. Last quarter I lost twenty minutes hunting for the exact kubectl rollout-restart incantation a teammate had pasted in Slack three weeks earlier. That was the day I decided every command worth running twice belongs in a task runner, not in my fingertips.
The good news is that turning a messy bash history into a clean Makefile or justfile is exactly the kind of grunt work AI is great at. The catch is that AI is a fast junior engineer, not a senior one. It will happily generate a target that wipes a namespace with no confirmation, no scoping, and no way to undo it. So the real skill isn’t “ask the model for a Makefile” — it’s knowing what to keep, what to gate, and what to never let it touch.
Why a task runner beats a wiki page
A pasted command in a wiki rots. Nobody updates it, the flags drift, and the one person who understood it leaves. A checked-in task runner lives next to the code, gets reviewed in pull requests, and runs the same way on every laptop and CI runner. When someone types make deploy-staging, they get your tested sequence, not their best guess at it.
Both Make and just give you named targets, variables, and dependency ordering. Make is everywhere already. just is purpose-built for command running — no implicit rules, no tab-versus-space landmines, and a cleaner syntax for parameters. I reach for just on new projects and Make where it already exists.
A Makefile that scopes its blast radius
Here is a real target set I use for a service. Notice the variables at the top, the .PHONY declaration so Make never confuses a target with a file, and a guard that runs before anything destructive.
.PHONY: help build test deploy-staging deploy-prod guard-prod restart
ENV ?= staging
NAMESPACE ?= myapp-$(ENV)
IMAGE ?= registry.internal/myapp:$(shell git rev-parse --short HEAD)
help: ## List available targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| awk 'BEGIN {FS = ":.*?## "}; {printf " %-16s %s\n", $$1, $$2}'
build: ## Build the container image
docker build -t $(IMAGE) .
test: ## Run the test suite
go test ./...
deploy-staging: build test ## Build, test, and deploy to staging
kubectl -n $(NAMESPACE) set image deploy/myapp app=$(IMAGE)
guard-prod:
@if [ "$(ENV)" != "prod" ]; then \
echo "Refusing: ENV is '$(ENV)', not prod"; exit 1; fi
@read -p "Deploy $(IMAGE) to PROD namespace $(NAMESPACE)? [yes/N] " ok; \
[ "$$ok" = "yes" ] || { echo "Aborted."; exit 1; }
deploy-prod: guard-prod build test ## Deploy to prod (requires typed confirmation)
kubectl -n $(NAMESPACE) set image deploy/myapp app=$(IMAGE)
@echo "Back-out: kubectl -n $(NAMESPACE) rollout undo deploy/myapp"
Three things are doing the heavy lifting. NAMESPACE is derived from ENV, so the blast radius is scoped to one namespace and you can’t fat-finger a deploy into the wrong place. guard-prod forces a typed yes — not a y, a full yes — before prod is touched. And deploy-prod prints the back-out command on success, so the rollback is never something you have to remember under pressure. The guard is a hard dependency of the deploy target; you cannot reach the destructive step without passing through it.
Pro Tip: Make the help target your default and self-documenting. The ## comment trick above turns every target’s comment into a printed menu, so make help is always accurate. A task runner nobody can read is a wiki page with extra steps.
The same idea in a justfile
just expresses the same intent with less ceremony. Recipes take real parameters, and the [confirm] attribute gives you a built-in prompt instead of a hand-rolled read loop.
image := "registry.internal/myapp:" + `git rev-parse --short HEAD`
# Show all recipes
default:
@just --list
build:
docker build -t {{image}} .
test:
go test ./...
# Deploy to a named environment (defaults to staging)
deploy env="staging": build test
kubectl -n myapp-{{env}} set image deploy/myapp app={{image}}
# Deploy to prod — prompts before running
[confirm("Deploy to PROD? Type y to continue.")]
deploy-prod: build test
kubectl -n myapp-prod set image deploy/myapp app={{image}}
@echo "Back-out: kubectl -n myapp-prod rollout undo deploy/myapp"
just deploy hits staging. just deploy prod is possible but explicit — the environment is a parameter you have to type. just deploy-prod adds the [confirm] gate on top. Same three properties as the Makefile: scoped namespace, a confirmation gate, a printed back-out path. The syntax just gets out of the way more.
Turning a messy bash history into recipes
This is where AI earns its keep. Pull the relevant lines out of your shell history and hand them over with tight instructions. A prompt I actually use:
Here are 15 commands from my bash history. Group them into a justfile with sensible recipe names. Pull repeated values (image tag, namespace, region) into variables. Anything that deletes, restarts, scales to zero, or writes to prod must NOT run automatically — add a
[confirm]attribute and a comment noting the back-out command. Do not invent commands I didn’t give you. Ask me before assuming any value.
That last sentence matters. Left alone, the model will cheerfully fabricate a terraform apply target you never asked for. Treat its output like a junior engineer’s first PR: useful draft, mandatory review. I read every generated target and ask one question of each — “what’s the worst this does if I run it with the wrong argument?” If I can’t answer, it doesn’t merge.
For this kind of conversion I lean on whatever assistant is already in my editor — Cursor and GitHub Copilot both do it well in-context, and Claude handles the longer “here’s my whole history, organize it” pass. If you want reusable starting points instead of writing the prompt fresh each time, the prompt library and the prompt packs have task-runner templates worth lifting.
Pro Tip: Never paste prod credentials, kubeconfigs, or .env files into the model to “help it understand the environment.” The recipe should reference a credential by name (an env var, a vault path) and let the human supply it at run time. The model writes the plumbing; it never holds the keys.
Keep destructive recipes honest
A few habits keep the gates from becoming theater. Make the confirmation describe the actual blast radius — “Delete namespace myapp-prod and all 14 pods?” beats a generic “Are you sure?”. Default every environment variable to the safest value, so a bare make deploy or just deploy lands in staging, never prod. And print the back-out command on the same line as the action so the rollback is one copy-paste away.
These are the same principles I apply to anything AI suggests, not just task runners. A human owns the decision, the action is scoped to the smallest reasonable target, and there’s always a path back. I wrote up the broader version of this in ChatOps approval gates for AI-suggested actions, and the same discipline shows up in our code review and incident response tooling — the model proposes, the gate holds, the human approves.
Make recipes idempotent so reruns are safe
Half the value of a task runner is that you can rerun it without thinking. That only holds if the recipes are idempotent — running make migrate twice shouldn’t double-apply anything, and a half-finished deploy should be safe to rerun from the top. When I have the model generate recipes, I explicitly ask it to make each one safe to run twice. If it reaches for mkdir, I want mkdir -p; if it appends to a file, I want a check-then-write. There’s a deeper treatment of this in writing idempotent automation scripts, and it pairs naturally with task runners: idempotent recipes plus confirmation gates means the worst outcome of a mistaken rerun is usually “nothing happened.”
Wiring it into the team
Once the file exists, the payoff compounds. CI calls the same make test and make build your laptop does, so “works on my machine” stops being a sentence anyone says. New hires read just --list instead of asking which script to run. And because it’s all in version control, every change to how you deploy goes through review like any other code. The full automation category has more on building these workflows out.
Conclusion
A Makefile or justfile turns tribal shell knowledge into something your whole team can run the same way every time. AI makes the conversion from messy history to clean recipes nearly free — but it’s a fast junior engineer, and the senior judgment is yours. Scope the blast radius, gate anything that touches prod behind a typed confirmation, print the back-out path, and never hand the model your credentials. Do that, and just deploy-prod becomes one of the safest commands in your repo instead of the scariest.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.