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 typingjust <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
justfilefor 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:
justpasses 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 yourjustfileis intended for diverse environments. - Quoting: Complex commands with quotes within quotes can be tricky.
justitself doesn’t interpret shell quoting; the underlying shell does. Double-check your quoting, especially when passing arguments throughjust. @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 (likerm -rfor internal script calls).- Default Shell: If not specified via
--shell,justuses the default system shell (often/bin/shor/bin/bashon Linux/macOS, andcmd.exeor 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
evalor carefully construct your commands. - Dependencies are Run First: When
just recipe_ais called andrecipe_adepends onrecipe_b,recipe_bwill always run beforerecipe_a, even ifrecipe_bwas 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 recipemy-recipe: | name="default", runningjust my-recipe --name="custom"will use"custom". Runningjust my-recipe customwill also use"custom". - Boolean Flags: Boolean flags defined with
?(e.g.,--dry-run?) are automatically set totrueif the flag is present on the command line, andfalseotherwise. You don’t need to provide a value like--dry-run=true. justfileLocation:justsearches forjustfilein the current directory and then recursively up the directory tree. If you have multiplejustfiles, use the-fflag to specify which one to use.