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.