just
Handy command runner for project-specific recipes
just is a command runner that saves and runs project-specific commands called recipes, with a syntax inspired by Make but simpler. It runs locally as a single binary.
Key features
- Simple recipe syntax
- Cross-platform
- No build artifacts
- Shell-agnostic
Pros & cons
Strengths
- Simpler than Make
- Single static binary
- Great error messages
Trade-offs
- No dependency tracking
- Extra tool to install
just replaces
Last reviewed Aug 26, 2026 · 837 words
My homelab repo has a 60-line justfile and no Makefile, and the difference shows the first time someone else touches the box. just on its own prints every recipe with its comment, just backup runs the backup without anyone reading a README, and just restore asks for confirmation before it overwrites live data. Make can be bent into doing all of that; just does it by default, from a single 5 MB binary with no dependencies, on Linux, macOS and Windows.
Make was never a command runner
Make is a build system that people use as a command runner, and the seams show: every target is assumed to be a file, so you sprinkle .PHONY everywhere; recipes must be indented with tabs; variables and shell quoting follow rules that have surprised people since 1976; and make with no arguments builds whatever happens to be first. just keeps the shape (recipes, a file at the repo root, run from any subdirectory) and drops the file-dependency model entirely. Recipes take arguments, indentation can be spaces, errors say what went wrong with a line number, and just --list is the built-in help. If you only ever typed make up and make logs, you were already using a command runner; you were just using a bad one.
A justfile for a compose-based homelab
This is a trimmed version of mine. It assumes a .env next to the compose file, which is the pattern from the compose patterns post:
set dotenv-load
# list recipes
default:
@just --list
# start one service, or everything
up service='':
docker compose up -d {{service}}
# follow logs for a service
logs service:
docker compose logs -f --tail 200 {{service}}
# pull new images and restart what changed
update:
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
# snapshot the data directory
backup:
restic backup /srv/data --tag compose
[confirm("Restore over live data?")]
restore snapshot='latest':
docker compose down
restic restore {{snapshot}} --target /
docker compose up -d
just up immich starts one service, just up starts all of them, just logs jellyfin tails logs, and just restore will not proceed until you type y. The whole file is readable by someone who has never seen just before, which is the actual selling point.
The 3 features that do the work
Arguments with defaults are the first. up service='' makes the parameter optional; logs service makes it required, and calling it without one fails with a clear message instead of a mysterious empty docker compose logs. Second is set dotenv-load, which reads .env into the environment for every recipe, so restic finds RESTIC_REPOSITORY and RESTIC_PASSWORD without you exporting them in your shell or duplicating them in a second file. Third is [confirm], a one-line guard for anything destructive. Beyond those, [group('name')] sorts the --list output, backtick expressions capture command output into variables, and a recipe whose first line is a shebang runs as one script instead of line-by-line. just --fmt --unstable formats the file, and just --choose gives you an fzf picker if fzf is installed.
What just refuses to do
It does not track dependencies. There is no "rebuild this if that changed", and there never will be; that is Make's job and just's maintainer has said so repeatedly. Each recipe line runs in a fresh shell, so cd on one line does not affect the next (use a shebang recipe or &&). It is not a scheduler (that is cron or a systemd timer calling just backup), not a CI system, and not a replacement for a proper deployment tool once you outgrow one box. The remaining con is real too: it is one more binary your collaborators need, and a stranger who clones the repo gets command not found where make would at least have existed. The mitigation is a one-line install in the README.
Installing it on a server
Recent Debian and Ubuntu releases carry it in apt, Homebrew has it, cargo install just works anywhere Rust does, and the project publishes prebuilt binaries with an installer that drops one file wherever you point it:
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
Being one static Go-free Rust binary means the same command works on the Forgejo runner, the NAS and the laptop. 64 MB of RAM is the catalogue floor; in practice it uses a few megabytes and is gone before you notice it started.
What I'd do
Put a justfile at the root of the repo that holds your compose files, with default listing recipes, up, logs, update, backup and a confirmed restore. Keep secrets in .env, load them with set dotenv-load, and install just from the release binary on every machine that touches the repo. Keep Make for projects that actually compile things. For everything else, there is no reason to write another Makefile in 2026.
Compare just
1 head-to-head comparisons.
Similar developer tools & git apps
Excalidraw
Developer Tools & GitVirtual hand-drawn style whiteboard
Replaces Miro
lazygit
Developer Tools & GitSimple terminal UI for Git commands
Replaces GitKraken, Sourcetree
Hoppscotch
Developer Tools & GitOpen-source API development ecosystem
Replaces Postman, Insomnia
json-server
Developer Tools & GitFull fake REST API from a JSON file in seconds
Replaces Mockoon, Postman Mock
Strapi
Developer Tools & GitLeading open-source headless CMS
Replaces Contentful
NocoDB
Developer Tools & GitOpen-source Airtable alternative
Replaces Airtable