Why I Fell Back in Love with Script Runners (Thanks to justfile)

· 3 min read
toolingdeveloper-experience
Why I Fell Back in Love with Script Runners (Thanks to justfile)

I’ll admit it: somewhere along the way, I stopped caring about script runners.

For years, I’d been throwing commands into package.json scripts, copying shell commands from READMEs, or just… remembering things. It worked. Mostly. Until it didn’t, and I’d spend 10 minutes reconstructing that one docker compose command with the right env vars.

Then I rediscovered justfile, and suddenly I remembered why script runners exist in the first place.

The problem I forgot I had

Working on any non-trivial project means juggling multiple services, deployment environments, and toolchains. On any given day I might need to:

  • Spin up local infrastructure
  • Run database migrations
  • Execute test suites with specific configurations
  • Build and publish container images
  • Connect to staging databases through proxies

Each of these involves commands I mostly remember. The friction isn’t huge, but it’s constant. Death by a thousand paper cuts.

Enter justfile

just is a command runner. That’s it. No build system, no dependency graph, no PhD required. You write a justfile, define recipes, and run them with just <recipe-name>.

Here’s what sold me:

# Start all services for local development
dev: dependencies
    process-compose -f ./process-compose.yaml

# Run tests with coverage
test-coverage:
    uv run pytest --cov=app --cov-report=html -v

# Create a new database migration
db-migration message:
    uv run alembic revision --autogenerate -m "{{message}}"

# Publish API image to registry
publish-api env="staging":
    docker buildx build --platform linux/amd64 \
        -f Dockerfile.api \
        -t registry.example.com/api:{{env}} --push .

Then it’s just:

just dev
just test-coverage
just db-migration "add user roles"
just publish-api prod

No more “wait, what was that flag again?”

Why justfile over alternatives?

I’ve used Make. I’ve used npm scripts. I’ve used poetry scripts. Here’s why just won me over:

It’s the right level of power. Make is powerful but full of gotchas: tabs-vs-spaces errors, cryptic automatic variables, a syntax designed for C compilation in the 1970s. npm scripts are the opposite problem: JSON wasn’t meant for shell commands, so you end up escaping quotes inside quotes inside quotes. just sits in the middle: powerful like Make, but with modern ergonomics.

It’s polyglot. My projects span Python, TypeScript, and bash. just doesn’t care—it runs whatever shell commands you give it.

It has sensible defaults. Recipe arguments, environment variables, dotenv loading, conditional execution—all there, all obvious.

It documents itself. Run just --list and you get a clean overview of available commands. Your justfile is the documentation.

$ just --list
Available recipes:
    db-migration message # Create a new database migration
    dev                  # Start all services for local development
    publish-api env      # Publish API image to registry
    test-coverage        # Run tests with coverage

The Rediscovery

What I’d forgotten is that script runners aren’t about saving keystrokes (though they do). They’re about reducing cognitive load.

Every command I encode in a justfile is one less thing I need to remember. One less thing to explain to a teammate. One less thing to get wrong when you’re under pressure.

There’s also something satisfying about a well-organised justfile. It’s a living index of “things you can do with this project.” New team member? Read the justfile. Returning after a holiday? Read the justfile.

My Setup

These days, every project gets a justfile. The pattern I’ve settled on:

# Default recipe to display available commands
default:
    @just --list

# Development
dev: ...
test: ...
lint: ...

# Database
db-migrate: ...
db-reset: ...

# Build & Deploy
build-api: ...
publish-api: ...

# Utilities
clean: ...
status: ...

Nothing revolutionary. That’s the point.

The abstraction tradeoff

There’s a risk worth acknowledging: the more we abstract, the less people learn what’s underneath. When just dev hides a multi-step process involving docker, process-compose, and dependency installation, newcomers might not understand what’s actually happening. And when things break (they always do), they won’t know where to start debugging.

The mitigation? Keep your recipes readable. Use comments. Make sure at least a few people on the team understand the full picture. A justfile should lower the barrier to getting started, not become a black box that only one person can maintain.

Try It

If you’ve drifted away from script runners, or if you’ve been suffering through Makefiles or npm script spaghetti, give just a try. It installs in seconds:

brew install just    # macOS
cargo install just   # anywhere with Rust

No configuration required, and it will immediately start paying dividends.

Sometimes the best tools are the ones that do one thing well and get out of your way.

just does exactly that.