Install
# Debian / Ubuntu
curl --proto '=https' --tlsv1.2 -sSf \
https://just.systems/install.sh | sudo bash -s -- --to /usr/local/bin
# macOS
brew install just
# Arch
sudo pacman -S just
# Or via cargo / mise (see /tutorials/mise-polyglot-runtime-versions.html)
cargo install just
mise use -g just@latest
The first Justfile
Drop a file called Justfile (capital J; matches Makefile) in the project root:
# Justfile
# Default recipe (runs when you type "just" with no arg)
default: help
# Show available recipes
help:
@just --list --unsorted
# Build the project
build:
cargo build --release
# Run tests
test:
cargo test
# Run lint + tests (depends on lint then test)
ci: lint test
@echo "ci passed"
lint:
cargo fmt --check
cargo clippy -- -D warnings
# Build + run
run: build
./target/release/myapp
Now:
just # runs "default" -> help
just --list # show recipes with their first comment as documentation
just test
just ci
No tabs vs spaces issues. No .PHONY declarations. No silent failure when a recipe forgets the recipe-prefix tab. Recipes are just scripts.
Parameters
# Recipes can take args
deploy environment:
echo "Deploying to {{environment}}"
kubectl --context {{environment}} apply -f k8s/
# With defaults
greet name="world":
echo "Hello, {{name}}!"
# Variadic
sum +numbers:
@echo {{numbers}} | tr ' ' '+' | bc
just deploy staging
just greet # "Hello, world!"
just greet Amir # "Hello, Amir!"
just sum 1 2 3 4 5 # "15"
Variables and conditional logic
# Top-of-file variables
project := "myapp"
version := `git describe --tags --always`
date := `date +%Y%m%d`
release:
cargo build --release
cp target/release/{{project}} dist/{{project}}-{{version}}-{{date}}
# OS-conditional
clean:
{{ if os() == "windows" { "del /Q target" } else { "rm -rf target" } }}
# Environment vars
deploy env="staging" sha=`git rev-parse HEAD`:
DEPLOY_ENV={{env}} DEPLOY_SHA={{sha}} ./scripts/deploy.sh
Per-recipe shell
Recipes are bash by default; per-recipe you can switch:
set shell := ["bash", "-euo", "pipefail", "-c"]
# A Python recipe
analyze:
#!/usr/bin/env python3
import json, sys
data = json.load(open("data.json"))
print(sum(item["price"] for item in data))
# A Node recipe
codegen:
#!/usr/bin/env node
const fs = require("fs");
const schema = require("./schema.json");
fs.writeFileSync("src/generated.ts", buildTypes(schema));
Dotenv loading
Drop a .env file; just loads it automatically (configurable):
set dotenv-load := true
# .env
# DATABASE_URL=postgres://localhost/dev
# API_KEY=...
migrate:
psql $DATABASE_URL -f migrations/latest.sql
call-api:
curl -H "X-API-Key: $API_KEY" https://api.example.com/users
Recipe dependencies
# Run in order
publish: ci build docs
cargo publish
# Parallel (24.x+)
test-all:
@just --parallel test-unit test-integration test-e2e
The --list output
just --list uses the comment immediately above each recipe as its documentation:
$ just --list
Available recipes:
build # Build the project
ci # Run lint + tests
deploy ENV # Deploy to a given environment
help # Show available recipes
test # Run tests
Per-project onboarding: new developer runs just --list, sees every documented task. Beats reading a scripts/ directory.
Common patterns worth knowing
# Confirmation before destructive actions
clean-all:
@echo "About to delete everything. Ctrl-C to cancel."
@sleep 3
rm -rf target node_modules dist
# Show a command's output but also save it
audit:
cargo audit | tee audit-{{date}}.log
# Cross-recipe vars: a recipe that prints a value for shell composition
git-sha:
@git rev-parse HEAD
# Then: TAG=$(just git-sha) docker build -t myapp:$TAG .
# Aliases for long recipe names
alias t := test
alias b := build
alias d := deploy
# Then: just t instead of just test
just vs alternatives
- Make — the elder. Universally installed. Tab-vs-spaces footgun; .PHONY ceremony; per-recipe shell brittleness. Stay with Make if your project uses it heavily for actual file-build dependency tracking (the original use case); switch to just for ad-hoc task running.
- npm scripts / cargo aliases — language-specific. Fine within one ecosystem; less useful for multi-language repos.
- mise tasks (see that tutorial) — bundles tasks with version management. If you already use mise, the tasks feature is sufficient; for projects that don't need version management, just is lighter.
- Taskfile (go-task) — very similar to just but YAML-based. Pick on syntax preference.
- Earthly — for build automation with caching; bigger tool, different scope.
For "I want one document at the project root that lists every command this project's developers might want to run, in syntax that doesn't bite me," just is the cleanest option in 2026.