Skip to content
Go back

Just vs Make in 2026

By SumGuy 8 min read
Just vs Make in 2026
Contents

The Task Runner Wars: Still Raging in 2026

Just wins for home lab automation and personal projects, and Make wins the moment you’re touching an existing large project that already depends on file-based dependency tracking.

You’ve probably got a Makefile in that monorepo. It’s doing exactly what it’s always done, automating the annoying manual stuff. Run tests. Deploy things. Generate boring config. It works. Nobody complains (much).

Then someone shows you Just. And suddenly you’re staring at a Justfile wondering if you’ve been overcomplicating task automation for the last decade.

Make is 1976. It was written by people who thought tabs were a good idea. Just landed in 2016. It was written by people who thought “hey, maybe we should make this less arcane.” Both work. Both have opinions. Both will make you curse at least once.

If you’re running a home lab, maintaining your own services, or just tired of memorizing magic Make incantations, this is your decision tree.


Should Your Home Lab Use Just or Make?

MakeJust
SyntaxTabs requiredPlain indentation
ParametersShell hacks, read promptsNative, {{var}} interpolation
Dependency trackingFile modification timesNone, task runner only
Built-in docsNonejust --list shows recipes
AvailabilityEverywhere since 1976Newer, since 2016

Make: The Hammer That Refuses to Break

Make is everywhere. It’s probably older than your Linux distro. Your entire build system probably depends on it without you even knowing.

The core idea is simple: recipes that depend on files. You want build: first, you need src/*.c to exist. Make checks modification times, skips what’s already done, builds what’s changed. That’s useful.

For home projects? Less useful. You’re not compiling C. You’re running Docker, deploying Ansible, maybe regenerating static site configs. Files don’t matter as much. You just want to run stuff.

Here’s a typical home-lab Makefile:

.PHONY: help build deploy test
help:
@echo "Available commands:"
@echo " make build - Build Docker image"
@echo " make deploy - Deploy to prod"
@echo " make test - Run tests"
@echo " make clean - Remove artifacts"
build:
docker build -t myapp:latest .
@echo "Built myapp:latest"
deploy: build
docker push myapp:latest
ssh root@prod "docker pull myapp:latest && docker compose up -d"
test:
pytest tests/
clean:
rm -rf build/ dist/ .pytest_cache/

It works. You run make deploy and it builds, pushes, and deploys. Pretty straightforward.

Now the pain points. Make uses tabs. Not spaces. Actual tab characters. Miss one and the entire thing explodes with a cryptic error that makes you Google “makefile tab error” at 2 AM like you’re seeing it for the first time (you’re not, Make just makes you forget).

Variable syntax is baroque: $(VAR), ${VAR}, @, .PHONY, recipe order is weird if you’re not used to it. And passing parameters? Oof.

deploy:
@read -p "Enter target (staging/prod): " target; \
docker push myapp:latest; \
ssh root@$$target "docker pull myapp:latest && docker compose up -d"

You’re escaping shell variables with $$. You’re using @ to suppress echo. You’re reading from stdin inside a Make recipe because Make doesn’t have a native argument passing system. It’s a band-aid on a band-aid.


Just: The Simplified Alternative

Just is a task runner that takes Make’s file-based dependency model, throws most of it in the trash, and gives you a cleaner, more scriptable tool.

Same Justfile concept, but the syntax is much less weird:

Terminal window
# Build Docker image
build:
docker build -t myapp:latest .
echo "Built myapp:latest"
# Deploy to environment
deploy target="prod": build
docker push myapp:latest
ssh root@{{target}} "docker pull myapp:latest && docker compose up -d"
# Run tests
test:
pytest tests/
# Clean artifacts
clean:
rm -rf build/ dist/ .pytest_cache/

Notice what’s different:

  1. No tabs. Spaces are fine. Just uses indentation like every modern language.
  2. Parameters are explicit. deploy target="prod" means the recipe takes a target parameter, defaults to prod. You use it with {{target}}. Clean.
  3. Recipe ordering is clear. deploy target="prod": build means “run the build recipe first, then deploy.” Dependencies are listed right there, not buried in .PHONY declarations.
  4. No shell escaping hell. String interpolation with {{variable}} feels natural. No $$ nonsense.

Call it with just deploy staging and boom, it runs deploy with target=staging. Try that with Make without writing weird shell loops.


Where the Rubber Meets the Road

Let’s look at some realistic home-lab tasks and how they differ.

Scenario 1: Multi-Step CI Pipeline

You’re deploying your blog. Generate images, build Astro, upload to CDN.

Make:

.PHONY: generate-images build deploy
generate-images:
python3 tools/gen_images.py
build: generate-images
npm run build
deploy: build
aws s3 cp public/ s3://my-bucket/blog/ --recursive
images:
@python3 tools/gen_images.py

Just:

Terminal window
# Generate featured images via ComfyUI
generate-images:
python3 tools/gen_images.py
# Build Astro site
build: generate-images
npm run build
# Deploy to S3
deploy: build
aws s3 cp public/ s3://my-bucket/blog/ --recursive

Both work. Just is slightly cleaner to read. Make wins if you care about file modification times (you probably don’t for this).

Scenario 2: Parameterized Deployment

You’ve got staging and prod. Different configs.

Make (painful):

.PHONY: deploy
deploy:
@read -p "Target (staging/prod): " target; \
if [ "$$target" = "prod" ]; then \
echo "Deploying to PROD..."; \
docker compose -f docker-compose.prod.yml up -d; \
else \
echo "Deploying to staging..."; \
docker compose -f docker-compose.staging.yml up -d; \
fi

Just (obvious):

Terminal window
deploy target="staging":
@echo "Deploying to {{target}}..."
docker compose -f docker-compose.{{target}}.yml up -d
# Call with: just deploy prod

Just wins here. Not even close. Make’s parameter handling is kludgy because it was never designed for it.

Scenario 3: Environment Variables

You need to inject secrets.

Make:

.PHONY: run
run:
export POSTGRES_URL=$$POSTGRES_URL; \
export REDIS_HOST=$$REDIS_HOST; \
docker compose up -d

Just:

run:
#!/usr/bin/env bash
export POSTGRES_URL=$POSTGRES_URL
export REDIS_HOST=$REDIS_HOST
docker compose up -d

Both require envvars to exist in your shell. Just lets you use real shell scripts (with #!/bin/bash headers) without escaping. Make’s line-continuation-with-semicolons approach is clunky.


Ecosystem & Tooling

Make is everywhere. Every C project, every large open-source repo, probably uses it. If you’re contributing to established projects, you’re touching Makefiles.

Just is gaining traction in Rust projects and newer home-lab tools. It’s becoming the default for single-project task automation. But it’s not universal yet.

Just advantages:

Make advantages:

For home labs, this tips toward Just. You’re not compiling C. You don’t need Make’s file-tracking model. You want clean parameter passing and readability.


When to Pick Each

Pick Make if:

Pick Just if:

For home labs specifically? Just wins. You’re orchestrating services and scripts, not building binaries. Just’s parameter system, cleaner syntax, and built-in help make it the better default.

The Make knowledge doesn’t go away. You’ll still edit Makefiles in established projects. But for your own stuff, the Justfile is probably the right call in 2026.


The Decision

You don’t think about task runners until they break. A Makefile that works stays invisible. A Justfile that’s well-organized becomes documentation.

If you’re maintaining a home lab with multiple services, trying to automate boring tasks, or just tired of typing long shell commands repeatedly, Justfile. Clean syntax, sensible defaults, zero tabs.

If you’re jumping into an existing large project, or you need true file-based dependency tracking for build systems, Make.

And honestly? Most small teams will have both eventually. Just for your new stuff, Make for everything else. It’s not a betrayal of either tool. It’s pragmatism. Pick the right hammer for the job, and don’t waste time arguing about which hammer is objectively better.

Your 2 AM self will appreciate whichever you choose, as long as the deployments work.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
direnv + asdf vs mise
Next Post
Helix vs Neovim: Modal Editor Showdown

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts