$linuxjunkies
>

Use just as a Modern Task Runner

Learn to use just as a project task runner: write justfiles, define variables, chain recipe dependencies, and manage tasks across multiple projects.

BeginnerUbuntuDebianFedoraArch8 min readUpdated June 7, 2026

Before you start

  • A terminal and a user account with sudo access for package installation
  • Basic familiarity with the command line and shell scripting
  • An existing project directory to add a justfile to

just is a command runner that stores project tasks in a justfile. Think of it as a Makefile without the build-system baggage — no implicit rules, no tab-character traps, no automatic dependency tracking on files. You write named recipes, run them with just recipename, and that's the contract. It's cross-platform, actively maintained, and has become the de-facto standard task runner in many Rust, Python, and Go projects.

Installing just

Debian / Ubuntu

just is packaged in Ubuntu 24.04 and Debian 13 (Trixie) onwards. On older releases, install from the upstream binary or via cargo.

sudo apt install just

Fedora / RHEL family

sudo dnf install just

Arch Linux

sudo pacman -S just

Any distro — via cargo

cargo install just

Any distro — prebuilt binary

curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin

Confirm the installation:

just --version

Your First justfile

Create a file named justfile (or .justfile) in your project root. just searches upward from the current directory, so you only need one file per project tree.

cat > justfile <<'EOF'
# Build the project
build:
    cargo build --release

# Run tests
test:
    cargo test

# Remove build artifacts
clean:
    cargo clean
EOF

Run a recipe:

just build

List all available recipes:

just --list

Output will look similar to:

Available recipes:
    build  # Build the project
    clean  # Remove build artifacts
    test   # Run tests

The leading four spaces before each command are plain spaces — not tabs. This is one of just's biggest quality-of-life improvements over make.

Variables and Interpolation

Variables are declared at the top level with := and interpolated with {{variable}}.

cat > justfile <<'EOF'
version := "1.4.2"
registry := "ghcr.io/myorg"
image := registry + "/myapp:" + version

build-image:
    docker build -t {{image}} .

push-image: build-image
    docker push {{image}}
EOF

You can override any variable on the command line without editing the file:

just version=2.0.0 build-image

Environment variables and .env files

just can load a .env file automatically. Add this line at the top of your justfile:

set dotenv-load

Now any variable in .env is available to recipes as a shell environment variable, not as a just variable — the distinction matters for interpolation vs. shell expansion.

Recipe Parameters

Recipes accept positional parameters with optional defaults.

cat > justfile <<'EOF'
# Deploy to a named environment, default: staging
deploy env="staging":
    echo "Deploying to {{env}}"
    ./scripts/deploy.sh {{env}}

# Migrate database; pass a specific revision or 'head'
db-migrate rev="head":
    alembic upgrade {{rev}}
EOF
just deploy production
just db-migrate 3a9c12f

Recipe Dependencies

List dependencies before the colon body to run them first. Dependencies run in order, left to right, and each runs only once per just invocation even if referenced multiple times.

cat > justfile <<'EOF'
lint:
    ruff check .

format:
    ruff format .

test: lint
    pytest -q

ci: format lint test
    echo "All checks passed"
EOF
just ci

Running just ci executes format, then lint (once, despite being a dependency of both ci and test), then test, then the echo.

Silencing and continuing on error

Prefix a command with @ to suppress echoing it, or with - to continue even if it fails.

clean:
    -rm -rf dist/
    @echo "Clean done"

Conditional Logic and Shell Features

Each line in a recipe is a separate shell invocation by default. To write multi-line shell logic, use a shebang to drop into a single shell session.

check-env:
    #!/usr/bin/env bash
    set -euo pipefail
    if [[ -z "${DATABASE_URL:-}" ]]; then
        echo "ERROR: DATABASE_URL is not set" >&2
        exit 1
    fi
    echo "Environment looks good"

just also provides a built-in conditional expression for variable assignment:

os := if os() == "macos" { "darwin" } else { "linux" }

download-tool:
    curl -L https://example.com/tool-{{os}} -o ~/.local/bin/tool
    chmod +x ~/.local/bin/tool

Working Across Multiple Projects

just walks up the directory tree looking for a justfile, so you can run project recipes from any subdirectory. For managing multiple repos or a monorepo, two patterns work well.

Pattern 1 — Root justfile delegates to subdirectories

cat > justfile <<'EOF'
# Run any recipe in the frontend workspace
frontend *args:
    cd frontend && just {{args}}

# Run any recipe in the backend workspace
backend *args:
    cd backend && just {{args}}

# Build everything
all: (frontend "build") (backend "build")
EOF
just frontend test
just backend deploy staging

Pattern 2 — Global justfile for personal shortcuts

Store a global justfile in ~/.justfile and invoke it from anywhere with:

just --global-justfile recipename

Add an alias to your shell config so you don't have to type the flag every time:

echo "alias jj='just --global-justfile'" >> ~/.bashrc
source ~/.bashrc

Verifying Everything Works

# Check just can parse your justfile without running anything
just --dry-run ci

--dry-run prints every command that would execute without running a single one. Use it to audit complex dependency chains before committing them to CI.

# Dump all variables and their values
just --evaluate

Troubleshooting

  • "error: Expected end of file, but found ..." — You have a syntax error. Common cause: using = instead of := for variable assignment.
  • Recipe silently does nothing — Check that your recipe body lines are indented with spaces, not tabs. While just does accept tabs, mixing them causes confusing parse failures on some versions.
  • Environment variable not visible in recipe — Each recipe line runs in a fresh shell. Export the variable explicitly, use set dotenv-load, or switch the recipe to a shebang block (#!/usr/bin/env bash) so all lines share one shell process.
  • just picks up the wrong justfile — Use just --justfile /path/to/justfile recipename to be explicit, or check which file just --list resolves to from your current directory.
  • Old package version missing features — Shebang recipes, set dotenv-load, and variadic *args require just 1.0+. Check with just --version and update via cargo install just if your distro ships an older build.
tested on:Ubuntu 24.04Fedora 40Arch rollingDebian 13

Frequently asked questions

How is just different from make?
make is a build system that tracks file timestamps and has implicit rules. just is purely a task runner with no file tracking, no implicit rules, and no tab-only indentation requirement — it's simpler and less error-prone for running commands.
Can I use just in CI/CD pipelines?
Yes. Install just in your CI environment the same way you would locally, then call just recipename in your pipeline steps. This keeps CI scripts and local dev workflows in sync.
Does just support Windows?
Yes, just has first-class Windows support with prebuilt binaries. Recipes use cmd.exe or PowerShell by default on Windows unless you specify a shebang line pointing to bash or another interpreter.
How do I pass multiple arguments to a recipe?
Use variadic parameters: declare the last parameter with a * prefix (e.g., test *flags) and all extra arguments are concatenated into that variable as a single string.
Can just load secrets from a .env file automatically?
Yes. Add set dotenv-load at the top of your justfile and just will read a .env file in the same directory, exposing its contents as shell environment variables inside recipes.

Related guides