Just Command Runner Installation and Usage

Just is a handy command runner written in Rust that uses a justfile to define project-specific commands called recipes. Unlike Makefiles, Just focuses purely on running commands rather than build dependency tracking, making it simpler and more predictable for project automation on Linux.

Prerequisites

  • Linux (Ubuntu/Debian or CentOS/Rocky)
  • Basic shell knowledge
  • A project directory to automate

Installing Just

# Install via cargo (Rust package manager)
cargo install just

# Ubuntu/Debian - prebuilt binary via apt
curl -fsSL https://just.systems/install.sh | bash -s -- --to /usr/local/bin

# Or download directly from GitHub releases
curl -sL https://github.com/casey/just/releases/latest/download/just-x86_64-unknown-linux-musl.tar.gz | \
  tar -xz -C /usr/local/bin just

# macOS via Homebrew (for teams with mixed environments)
brew install just

# Verify installation
just --version

Enable shell completion:

# Bash completion
just --completions bash > /etc/bash_completion.d/just

# Zsh completion
just --completions zsh > /usr/local/share/zsh/site-functions/_just

# Fish completion
just --completions fish > ~/.config/fish/completions/just.fish

Basic Justfile Syntax

Create a justfile in your project root:

# justfile

# Default recipe - shown when running `just` without arguments
default:
    @just --list

# Simple recipe
build:
    go build -o bin/app ./cmd/app

# Recipe with description (shown in list)
# Run all tests with coverage
test:
    go test ./... -v -coverprofile=coverage.out
    go tool cover -html=coverage.out -o coverage.html

# Recipe that calls another recipe
ci: lint test build
    echo "CI pipeline complete"

# Quiet recipe (suppress command echoing)
clean:
    @rm -rf bin/
    @rm -f coverage.out coverage.html
    @echo "Cleaned build artifacts"

Run recipes:

# List all recipes
just --list
just -l

# Run a recipe
just build

# Run default recipe
just

# Run multiple recipes in sequence
just clean build test

# Show what commands would run (dry run)
just --dry-run build

Recipes with Arguments and Variables

# Variables (set at top of justfile)
app_name := "myapp"
version := `git describe --tags --always`
build_date := `date -u +"%Y-%m-%dT%H:%M:%SZ"`

# Recipe with required argument
tag release_version:
    git tag -a {{release_version}} -m "Release {{release_version}}"
    git push origin {{release_version}}

# Recipe with default argument
serve port="8080":
    @echo "Starting server on port {{port}}"
    ./bin/{{app_name}} --port {{port}}

# Recipe with multiple arguments
deploy env="staging" region="us-east-1":
    @echo "Deploying {{app_name}} to {{env}} in {{region}}"
    ./scripts/deploy.sh {{env}} {{region}}

# Build with version info
build-release:
    go build \
        -ldflags "-X main.Version={{version}} -X main.BuildDate={{build_date}}" \
        -o bin/{{app_name}} \
        ./cmd/app

Call recipes with arguments:

# Pass argument to recipe
just tag v1.2.3

# Override default argument
just serve 9090

# Multiple arguments
just deploy production eu-west-1

Environment Variables

# Set environment variables for all recipes
export LOG_LEVEL := "info"
export APP_ENV := "development"

# Load from .env file
set dotenv-load

# Recipe that uses env vars
start:
    @echo "Starting in $APP_ENV mode"
    ./bin/app

# Override env for specific recipe
start-debug:
    LOG_LEVEL=debug ./bin/app

Conditionals and Control Flow

os := os()
arch := arch()

# Conditional based on OS
install-deps:
    #!/usr/bin/env bash
    set -euo pipefail
    if [ "{{os}}" = "linux" ]; then
        sudo apt-get install -y curl wget git
    elif [ "{{os}}" = "macos" ]; then
        brew install curl wget git
    else
        echo "Unsupported OS: {{os}}"
        exit 1
    fi

# Assert conditions before running
deploy-prod:
    #!/usr/bin/env bash
    set -euo pipefail
    if [ -n "$(git status --porcelain)" ]; then
        echo "Error: uncommitted changes detected"
        exit 1
    fi
    if [ "$(git branch --show-current)" != "main" ]; then
        echo "Error: must be on main branch to deploy"
        exit 1
    fi
    ./scripts/deploy.sh production

# Conditional recipe selection
build target="native":
    #!/usr/bin/env bash
    case "{{target}}" in
        native) go build -o bin/app ./cmd/app ;;
        linux)  GOOS=linux GOARCH=amd64 go build -o bin/app-linux ./cmd/app ;;
        darwin) GOOS=darwin GOARCH=arm64 go build -o bin/app-darwin ./cmd/app ;;
        *)      echo "Unknown target: {{target}}"; exit 1 ;;
    esac

Cross-Platform Support

Just provides built-in functions for cross-platform compatibility:

# Built-in OS and architecture detection
os_info:
    @echo "OS: {{os()}}"
    @echo "Arch: {{arch()}}"
    @echo "Home: {{home_directory()}}"

# Use PowerShell on Windows, bash on Unix
test:
    #!/usr/bin/env bash
    go test ./...

# Path separator handling
config_path := if os() == "windows" { "C:\\config" } else { "/etc/myapp" }

# Cross-platform binary name
binary := if os() == "windows" { "app.exe" } else { "app" }

build:
    go build -o bin/{{binary}} ./cmd/app

# Shell override per recipe
legacy-script:
    #!/bin/sh
    echo "Using POSIX sh"
    ls -la

Project Automation Patterns

Docker Workflow

image := "myapp"
registry := "registry.example.com"
tag := `git rev-parse --short HEAD`

# Build Docker image
docker-build:
    docker build -t {{image}}:{{tag}} -t {{image}}:latest .

# Push to registry
docker-push: docker-build
    docker tag {{image}}:{{tag}} {{registry}}/{{image}}:{{tag}}
    docker push {{registry}}/{{image}}:{{tag}}
    docker push {{registry}}/{{image}}:latest

# Run local development stack
up:
    docker compose up -d
    @echo "Services started. Run 'just logs' to follow logs"

down:
    docker compose down

logs service="":
    docker compose logs -f {{service}}

Database Migrations

db_url := env_var_or_default("DATABASE_URL", "postgres://localhost/myapp")

# Run migrations
migrate-up:
    migrate -database "{{db_url}}" -path ./migrations up

# Rollback one migration
migrate-down:
    migrate -database "{{db_url}}" -path ./migrations down 1

# Create new migration
migrate-create name:
    migrate create -ext sql -dir ./migrations -seq {{name}}

# Seed development database
seed:
    psql "{{db_url}}" < ./scripts/seed.sql

Kubernetes Deployment

namespace := "production"
kubeconfig := env_var_or_default("KUBECONFIG", "~/.kube/config")

# Apply manifests
k8s-apply:
    kubectl apply -f k8s/ --namespace {{namespace}}

# Watch rollout status
k8s-status:
    kubectl rollout status deployment/myapp --namespace {{namespace}}

# Full deploy flow
k8s-deploy: docker-push
    just k8s-apply
    just k8s-status
    @echo "Deployment complete"

Troubleshooting

Recipe not found:

# Check justfile is in current or parent directory
just --justfile /path/to/justfile list

# Just searches upward for justfile automatically
# Name must be exactly 'justfile' or 'Justfile' or '.justfile'

Argument parsing errors:

# Arguments with spaces need quoting
just serve "0.0.0.0:8080"

# Check recipe signature
just --show serve

Environment variable not loading:

# Ensure .env file exists and set dotenv-load is in justfile
ls -la .env

# Debug env loading
just --verbose start

Shebang scripts not executing:

# Ensure the script interpreter is installed
which bash
which python3

# Check shebang line syntax - must be first line of recipe body
# #!/usr/bin/env bash must have no leading spaces

Conclusion

Just provides a simple, Rust-powered command runner that eliminates Makefile's complexity while keeping project automation straightforward and readable. Its support for arguments, variables, environment files, and cross-platform detection makes it ideal for polyglot projects and teams. With a single justfile at your project root, all common tasks are discoverable and consistent across development, testing, and deployment workflows.