| name | just |
| description | Use just for command running and task automation. Prefer Justfiles over Makefiles. Keep recipes simple - delegate complex logic to scripts rather than embedding in recipes. |
just - Command Runner
IMPORTANT: just is the PREFERRED command runner for project task automation. Use Justfiles instead of Makefiles for new projects and task automation.
Core Philosophy
- Justfiles over Makefiles: Prefer just for task automation unless working with existing Make-based projects
- Simple recipes: Keep recipe definitions concise and readable
- Delegate to scripts: For complex logic, call external scripts rather than embedding in recipes
- Just as orchestrator: Use just to coordinate and invoke scripts, not to contain business logic
Basic Usage Patterns
Running Recipes
# List available recipes
just --list
just -l
# Run a recipe
just build
just test
# Run recipe with arguments
just deploy production
just run-test integration
# Run recipe from different directory
just --working-directory /path/to/project build
# Choose a different Justfile
just --justfile custom.just build
Recipe Definition Basics
# Simple recipe
build:
cargo build --release
# Recipe with description (shown in --list)
# Run all tests
test:
cargo test --quiet
# Recipe with dependencies (runs setup first)
deploy: build test
./scripts/deploy.sh
# Recipe with parameters
run-test test_name:
cargo test {{test_name}}
# Recipe with default parameter value
serve port="8080":
python -m http.server {{port}}
# Recipe with multiple parameters
deploy env branch="main":
./scripts/deploy.sh {{env}} {{branch}}
Delegation Pattern (PREFERRED)
Good - Delegates to script:
# Build the project
build:
./scripts/build.sh
# Run integration tests
test-integration:
./scripts/test-integration.sh
# Deploy to environment
deploy env:
./scripts/deploy.sh {{env}}
Avoid - Complex logic in recipe:
# ❌ Too complex - should be in a script
deploy env:
#!/usr/bin/env bash
set -euo pipefail
if [ "{{env}}" = "production" ]; then
echo "Deploying to production..."
git fetch origin
git checkout main
git pull
docker build -t app:latest .
docker push registry.example.com/app:latest
kubectl apply -f k8s/production/
kubectl rollout status deployment/app
else
echo "Deploying to staging..."
# ... many more lines ...
fi
Better - Simple orchestration:
# Deploy to production
deploy-prod:
./scripts/deploy.sh production
# Deploy to staging
deploy-staging:
./scripts/deploy.sh staging
# Generic deploy with validation
deploy env:
./scripts/validate-env.sh {{env}}
./scripts/deploy.sh {{env}}
Recipe Features
Dependencies
# Recipe runs after its dependencies
all: clean build test deploy
# Dependencies run in order specified
deploy: build test package
./scripts/deploy.sh
# Multiple dependency chains
full-deploy: lint build test docker-build docker-push k8s-deploy
Variables
# Set variables at top of file
version := "1.2.3"
registry := "registry.example.com"
image := registry + "/app:" + version
# Use in recipes
docker-build:
docker build -t {{image}} .
docker-push:
docker push {{image}}
# Environment variables
deploy:
API_KEY=$API_KEY ./scripts/deploy.sh
Conditional Execution
# Use shell conditionals in scripts
test:
./scripts/test.sh
# Or delegate to script
check-and-test:
./scripts/check-and-test.sh
Private Recipes
# Private recipe (not shown in --list, can't be run directly)
_setup:
./scripts/setup-env.sh
# Public recipe uses private recipe
deploy: _setup
./scripts/deploy.sh
Recipe Attributes
# Ignore errors (continue even if fails)
[ignore-errors]
lint:
./scripts/lint.sh
# Run in specific directory
[working-directory: 'frontend']
build-ui:
npm run build
# Confirm before running
[confirm]
deploy-prod:
./scripts/deploy.sh production
# Don't print recipe before running
[no-quiet]
status:
./scripts/status.sh
Common Patterns
Development Workflow
# Default recipe (runs when you type 'just')
default: check test
# Quick validation
check:
./scripts/check.sh
# Run all tests
test:
./scripts/test.sh
# Full CI workflow
ci: check test lint build
Build Automation
# Clean build artifacts
clean:
./scripts/clean.sh
# Build project
build:
./scripts/build.sh
# Full rebuild
rebuild: clean build
# Build with caching
build-cached:
./scripts/build.sh --cache
Multi-Environment Deployment
# Development environment
dev:
./scripts/run-dev.sh
# Staging deployment
deploy-staging:
./scripts/deploy.sh staging
# Production deployment (with confirmation)
[confirm]
deploy-prod:
./scripts/deploy.sh production
# Deploy to any environment
deploy env:
./scripts/validate-env.sh {{env}}
./scripts/deploy.sh {{env}}
Docker Workflows
# Build Docker image
docker-build:
./scripts/docker-build.sh
# Run container locally
docker-run:
./scripts/docker-run.sh
# Push to registry
docker-push: docker-build
./scripts/docker-push.sh
# Complete Docker workflow
docker: docker-build docker-push
Database Operations
# Run migrations
migrate:
./scripts/db-migrate.sh
# Rollback migration
migrate-rollback:
./scripts/db-rollback.sh
# Seed database
seed:
./scripts/db-seed.sh
# Reset database (dev only)
[confirm]
db-reset:
./scripts/db-reset.sh
Script Organization
Recommended Structure
project/
├── justfile
├── scripts/
│ ├── build.sh
│ ├── test.sh
│ ├── deploy.sh
│ ├── validate-env.sh
│ └── utils/
│ ├── common.sh
│ └── logging.sh
└── src/
└── ...
Script Best Practices
Make scripts executable:
chmod +x scripts/*.sh
Use shebang in scripts:
#!/usr/bin/env bash
set -euo pipefail
# Script logic here
Keep scripts focused:
# Good - single purpose
# scripts/build.sh
#!/usr/bin/env bash
set -euo pipefail
echo "Building project..."
cargo build --release
Source common utilities:
# scripts/deploy.sh
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/utils/common.sh"
source "$(dirname "$0")/utils/logging.sh"
log_info "Starting deployment..."
# Deployment logic
Advanced Features
Command Evaluation
# Backticks execute shell commands
commit := `git rev-parse --short HEAD`
timestamp := `date +%Y%m%d-%H%M%S`
# Use in recipes
tag:
echo "Tagging {{commit}} at {{timestamp}}"
Multi-line Constructs
# Multi-line variables
help := '''
Available commands:
build - Build the project
test - Run tests
deploy - Deploy to production
'''
# Print help
help:
@echo {{help}}
Settings
# Change shell (default is sh)
set shell := ["bash", "-c"]
# Make all recipes silent (@echo becomes echo)
set quiet := true
# Use PowerShell on Windows
set windows-shell := ["powershell.exe", "-c"]
# Use Python for recipes
set shell := ["python3", "-c"]
Imports
# Import recipes from other files
import 'ci.just'
import 'deploy.just'
# Use imported recipes
all: ci-check deploy-staging
Integration with Other Tools
With Cargo (Rust)
# Rust development workflow
check:
cargo check --quiet
test:
cargo test --quiet
clippy:
cargo clippy
build: check test clippy
cargo build --release
# Or delegate to script
rust-workflow:
./scripts/rust-workflow.sh
With npm/pnpm/yarn
# Install dependencies
install:
pnpm install
# Run dev server
dev:
pnpm run dev
# Build frontend
build:
pnpm run build
# Or use scripts
ui-build:
./scripts/build-ui.sh
With Docker
# Docker operations via scripts
docker-build:
./scripts/docker/build.sh
docker-run:
./scripts/docker/run.sh
docker-compose-up:
docker-compose up -d
With Git
# Git workflow helpers
sync:
git fetch origin
git pull
push: test
git push origin main
release version:
./scripts/release.sh {{version}}
Complete Workflow Examples
Workflow 1: Web Application
# Default - run dev server
default: dev
# Install dependencies
install:
./scripts/install.sh
# Development server
dev:
./scripts/dev.sh
# Run tests
test:
./scripts/test.sh
# Build for production
build:
./scripts/build.sh
# Deploy to staging
deploy-staging: test build
./scripts/deploy.sh staging
# Deploy to production
[confirm]
deploy-prod: test build
./scripts/deploy.sh production
Workflow 2: Rust Project
# Default - check code
default: check
# Fast check
check:
cargo check --quiet
# Run tests
test:
cargo test --quiet
# Lint code
lint:
cargo clippy
# Full validation
ci: test lint
./scripts/verify-formatting.sh
# Build release
build:
cargo build --release
# Install binary
install: build
cargo install --path .
Workflow 3: Monorepo
# Build all services
build-all:
./scripts/build-all.sh
# Test all services
test-all:
./scripts/test-all.sh
# Build specific service
build-service service:
./scripts/build-service.sh {{service}}
# Deploy specific service
deploy service env:
./scripts/deploy-service.sh {{service}} {{env}}
# Full deployment
deploy-all env: test-all build-all
./scripts/deploy-all.sh {{env}}
Common Commands Quick Reference
# List recipes
just --list
# Run recipe
just <recipe>
# Run with arguments
just <recipe> arg1 arg2
# Set variable
just var=value recipe
# Choose Justfile
just --justfile path/to/justfile recipe
# Work in different directory
just --working-directory /path recipe
# Dry run (show what would run)
just --dry-run recipe
# Verbose output
just --verbose recipe
# Choose recipe interactively
just --choose
Best Practices Summary
- Keep recipes simple: 1-3 lines max; delegate complex logic to scripts
- Use descriptive names:
deploy-prodnotdp,test-integrationnotti - Add comments: Describe what each recipe does
- Organize scripts: Keep scripts in
scripts/directory with logical subdirectories - Make scripts executable: Use
chmod +x scripts/*.sh - Use dependencies: Chain recipes with dependencies rather than duplicating commands
- Confirm destructive operations: Use
[confirm]for deployments, database resets, etc. - Private recipes for setup: Use
_recipenaming for internal setup steps - Default recipe: Provide sensible default (usually
devorcheck) - Document in comments: Add comments explaining complex workflows
When to Use Scripts vs. Recipes
Use Recipe Directly:
- Single command execution
- Simple chaining (3 commands or less)
- Setting environment variables for a command
- Quick aliases
Use Script (PREFERRED for most cases):
- Complex conditional logic
- Multiple steps with error handling
- Reusable logic across recipes
- Long command sequences (>3 commands)
- Anything requiring significant bash scripting
Justfile Template
# Set shell for all recipes
set shell := ["bash", "-c"]
# Variables
version := "1.0.0"
# Default recipe
default: check
# List all recipes
help:
@just --list
# Development
dev:
./scripts/dev.sh
# Testing
test:
./scripts/test.sh
# Build
build:
./scripts/build.sh
# Full CI check
ci: test build
./scripts/verify.sh
# Deploy to staging
deploy-staging: ci
./scripts/deploy.sh staging
# Deploy to production
[confirm]
deploy-prod: ci
./scripts/deploy.sh production
# Clean build artifacts
clean:
./scripts/clean.sh
Migration from Make
Key Differences from Make
- Not a build system: just is for running commands, not tracking file dependencies
- Recipes always run: Unlike Make targets, recipes run every time (no file timestamp checking)
- Simpler syntax: No need for
.PHONY, tabs vs. spaces issues, etc. - Better error messages: Clear, helpful error output
Converting Makefile to Justfile
Makefile:
.PHONY: build test deploy
build:
cargo build --release
test:
cargo test
deploy: build test
./deploy.sh
Justfile:
build:
cargo build --release
test:
cargo test
deploy: build test
./scripts/deploy.sh
Resources
- Official documentation: https://just.systems
- GitHub repository: https://github.com/casey/just
- Installation:
brew install just(macOS) orcargo install just