just Command Runner

just cheatsheet — define and run project commands with justfile. just build, just test, just deploy. Variables, parameters, dependencies, default recipes. Makefile alternative.

8 min read

What it is

just is a command runner that allows you to define and execute custom shell commands within a project, offering a simpler and more discoverable alternative to Makefiles or complex shell scripts.

Installation

Linux

sudo apt update && sudo apt install just
# or
sudo dnf install just
# or
cargo install just

macOS

brew install just
# or
cargo install just

Windows

Download the appropriate binary from the just releases page.

Alternatively, using Scoop:

scoop install just

Or using Cargo:

cargo install just

Core Concepts

  • justfile: The primary configuration file where recipes (commands) are defined. It’s typically located in the root of your project.
  • Recipe: A named set of shell commands defined in the justfile. You execute a recipe by typing just <recipe-name>.
  • Dependencies: Recipes can depend on other recipes. When you run a recipe, its dependencies are executed first.
  • Arguments: Recipes can accept arguments, allowing for dynamic command execution.
  • Variables: You can define variables in the justfile for reusability and configuration.
  • Shebang: Lines starting with #! are treated as shebangs, allowing you to specify the interpreter for a recipe (e.g., #!/usr/bin/env python3).

Commands / Usage

Listing Available Recipes

just

Lists all available recipes in the justfile with their descriptions.

Running a Simple Recipe

just build

Executes the build recipe.

Running a Recipe with Arguments

just run --production

Executes the run recipe, passing production as an argument.

just deploy staging

Executes the deploy recipe, passing staging as the first argument.

Running a Recipe with Named Arguments

just test --verbose --file tests/unit.rs

Executes the test recipe, passing named arguments --verbose and --file tests/unit.rs.

Running a Recipe with a Shebang

Assume justfile contains:

#!/usr/bin/env python3
py-hello:
    print("Hello from Python!")
just py-hello

Executes the py-hello recipe using python3.

Showing Recipe Help

just --help build

Displays the help message for the build recipe (if defined).

Listing All Recipes and Their Dependencies

just --list

Lists all recipes, their dependencies, and their descriptions.

Showing the Command for a Recipe

just --show build

Prints the shell commands that the build recipe would execute, without running them.

Listing Variables

just --variables

Lists all variables defined in the justfile.

Listing Dependencies for a Recipe

just --dependencies build

Lists the direct dependencies of the build recipe.

Listing All Dependencies for a Recipe

just --all-dependencies build

Lists all recipes that build depends on, directly or indirectly.

Running a Recipe from a Different justfile

just -f path/to/other/justfile test

Executes the test recipe from path/to/other/justfile.

Running a Recipe with a Custom Working Directory

just -C /app/backend build

Executes the build recipe with /app/backend as the current working directory.

Running Recipes with Specific Interpreters

bash-echo:
    echo "Hello from Bash!"

sh-echo:
    echo "Hello from Sh!"
just bash-echo

Executes bash-echo using the default shell (often Bash).

just --shell /bin/sh sh-echo

Executes sh-echo specifically using /bin/sh.

Running a Recipe with a Specific Environment Variable

just --env FOO=bar build

Runs the build recipe with the environment variable FOO set to bar.

Running a Recipe with Multiple Environment Variables

just --env FOO=bar --env BAZ=qux build

Runs the build recipe with FOO=bar and BAZ=qux.

Running a Recipe with an Environment File

Assume you have a .env file:

DATABASE_URL=postgres://user:pass@host:port/db
API_KEY=abcdef12345
just --dotenv .env build

Loads variables from .env into the environment before running the build recipe.

Running a Recipe with Explicit Dependencies

hello:
    echo "Hello!"

greet: hello
    echo "Greetings!"
just greet

Runs hello first, then greet.

Using Variables in Recipes

APP_NAME := my-awesome-app
VERSION := 1.0.0

build:
    echo "Building $(APP_NAME) version $(VERSION)..."
    docker build -t $(APP_NAME):$(VERSION) .
just build

Executes the build command using the defined variables.

Conditional Execution (using if in recipes)

default:
    @if [ "$1" = "production" ]; then \
        echo "Deploying to production!"; \
    else \
        echo "Deploying to staging: $1"; \
    fi
just default production

Outputs: Deploying to production!

just default staging

Outputs: Deploying to staging: staging

Reiterating Recipes (using ...)

lint:
    echo "Linting..."

format: lint
    echo "Formatting..."

check: format ...
    echo "All checks passed."

Running just check will execute lint, then format, then check.

Skipping Recipes (using !)

setup-db:
    echo "Setting up database..."

migrate: setup-db
    echo "Running migrations..."

deploy: !migrate
    echo "Deploying application..."

Running just deploy will not run migrate or setup-db.

Private Recipes (using @)

@print-secret:
    echo "This is a secret."

public-task: @print-secret
    echo "Running public task."

Running just public-task will execute @print-secret first, but print-secret itself cannot be directly invoked via just @print-secret.

Overriding Recipes

just --eval "echo 'Overridden command'"

Executes the provided string as a command, ignoring the justfile.

Setting Default Recipes

default: build

build:
    echo "Building..."

Running just without any arguments will execute the build recipe.

Displaying Output Silently (@)

silent-task:
    @echo "This message is silent."

Running just silent-task will execute the echo command, but the command itself (echo "This message is silent.") will not be printed to the terminal.

Setting Recipe Dependencies

build: clean
    echo "Building..."

clean:
    echo "Cleaning..."

Running just build first executes clean.

Recipe Arguments with Defaults

greet: | name="World"
    echo "Hello, $(name)!"
just greet

Outputs: Hello, World!

just greet --name="Alice"

Outputs: Hello, Alice!

Recipe Arguments with Types

add: | x:i y:i
    echo $(($x + $y))
just add --x=5 --y=10

Outputs: 15

Recipe Arguments with Boolean Flags

deploy: | --force -d --dry-run?
    if [ "$force" = "true" ]; then echo "Forcing deployment..."; fi
    if [ "$dry_run" = "true" ]; then echo "Dry run mode enabled."; fi
    echo "Deploying to $(d)..."
just deploy --force --dry-run

Outputs:

Forcing deployment...
Dry run mode enabled.
Deploying to ...

Note: d is not set here, so it’s empty.

just deploy -d production --force

Outputs:

Forcing deployment...
Deploying to production...

Common Patterns

Running Tests and Linting

test:
    pytest

lint:
    flake8 .

check: test lint
just check

Runs tests and then linting.

Building and Running a Docker Image

build-docker:
    docker build -t my-app:latest .

run-docker: build-docker
    docker run -p 8080:80 my-app:latest
just run-docker

Builds the Docker image and then runs the container.

Deploying to Different Environments

deploy: | env?
    @if [ "$env" = "production" ]; then \
        echo "Deploying to production..."; \
        ./scripts/deploy.sh production; \
    elif [ "$env" = "staging" ]; then \
        echo "Deploying to staging..."; \
        ./scripts/deploy.sh staging; \
    else \
        echo "Unknown environment: $env"; \
        exit 1; \
    fi
just deploy --env production

Executes the deployment script for the production environment.

Cleaning Build Artifacts

clean:
    rm -rf dist/ build/ *.pyc
just clean

Removes generated files.

Running a Python Script with Arguments

#!/usr/bin/env python3
run-script: | script_name="main.py" message="Hello"
    python $(script_name) --message "$(message)"
just run-script --message="World"

Runs python main.py --message="World".

Setting up a Development Environment

setup-dev:
    python -m venv .venv
    source .venv/bin/activate
    pip install -r requirements.txt

run-dev: setup-dev
    python app.py
just run-dev

Creates a virtual environment, installs dependencies, and then runs the app.

Gotchas

  • Shell Interpretation: just passes recipes directly to the system’s default shell. This means shell-specific syntax (like | for pipes, && for command chaining) works as expected. However, be mindful of cross-platform shell compatibility if your justfile is intended for diverse environments.
  • Quoting: Complex commands with quotes within quotes can be tricky. just itself doesn’t interpret shell quoting; the underlying shell does. Double-check your quoting, especially when passing arguments through just.
  • @ for Silence: The @ prefix before a command in a recipe suppresses the printing of that specific command. This is useful for making output cleaner, especially for commands you don’t want users to see directly (like rm -rf or internal script calls).
  • Default Shell: If not specified via --shell, just uses the default system shell (often /bin/sh or /bin/bash on Linux/macOS, and cmd.exe or PowerShell on Windows). Be aware of shell differences.
  • Variable Expansion: Variables are expanded before the command is executed. If you need shell-level variable expansion within a recipe, you might need to use eval or carefully construct your commands.
  • Dependencies are Run First: When just recipe_a is called and recipe_a depends on recipe_b, recipe_b will always run before recipe_a, even if recipe_b was already run as part of another dependency chain.
  • Recipe Argument Precedence: Named arguments (--name=value) take precedence over positional arguments (value). If you define a recipe my-recipe: | name="default", running just my-recipe --name="custom" will use "custom". Running just my-recipe custom will also use "custom".
  • Boolean Flags: Boolean flags defined with ? (e.g., --dry-run?) are automatically set to true if the flag is present on the command line, and false otherwise. You don’t need to provide a value like --dry-run=true.
  • justfile Location: just searches for justfile in the current directory and then recursively up the directory tree. If you have multiple justfiles, use the -f flag to specify which one to use.