hlfshell

justfiles can feel magical

I’ve long been a fan of task runners within projects - I find they’re practically required once you have a project or group operating beyond a certain level of complexity. There are just too many one liner terminal commands to memorize, too many differing syntaxes across dozens of tools operating across different stacks… it’s madness.

Typically engineers (at least the ones that eventually give up on the muscle memory approach) combat it with either a host of bash scripts or Makefiles through tons of .phony targets. Makefiles can be a bit obtuse in applications not actively using it for C/C++ management, and bash scripts can end up nightmarish in complexity and maintainability. Similarly there’s a lot of work to just get to the level of UI completeness that we are used to in our tools.

Enter just. Not only does it provide a sane interface, it supports multiple languages as a task runner. I’ve highly recommended it to a lot of developers and have begun getting several of them to absolutely adore how many things can be controlled by one syntaxically simple terminal command. Nesting them with justfile mods have made complex command structures intuitively simple.

In one of my latest contracts, I wowed a co-dev by having just ship grab the current git tag, build the app, build the dockerfile, publish it under the git tag to the container repository, then appropriately update the fly.toml and use flyctl (fly.io) to deploy it for you.

I thought it’d be neat to share some of my justfile setup for HiredCoach atm. I have a main justfile that basically directs you to a submodule, each of which is stored at justfiles/<module>.just

[main](๑•ᴗ•)⊃━~━☆゚> just

🚀 HiredCoach Dev Tools
========================

📦 dev      - Local dev tools & local service runtime
🔧 setup    - Install deps, verify tooling
🐋 deploy   - Build/publish Docker images
⚙️ config   - Push/Pull env config to GCS
🧪 test     - Run pytest
🏗️ infra    - Pulumi infra operations

Examples:
  just dev run
  just setup install
  just deploy build
  just infra app-up STACK=dev
  just config get ENV=dev

Run 'just --list' to see all modules and recipes.

Each module represents either complex interactions or the most common reasons I’m interacting with the codebase on a given day.

To deploy I just need to tag a release 1.x.x and use just deploy publish 1.x.x to build the application and the docker image, finally uploading the docker image.

[main](๑•ᴗ•)⊃━~━☆゚> just deploy
🐋 Deploy Module
================
  🔨 build           - Build Docker image (uses git tag or latest)
  🔄 rebuild         - Remove image then build
  📤 publish TAG     - Tag and push to Artifact Registry
  🏃 run-docker      - Run local container
  🗑️ docker-clean    - Remove local image
  🚢 ship ENV TAG    - Update image tag and deploy to environment (ENV=dev|prod)
[main](๑•ᴗ•)⊃━~━☆゚>

I am using Pulumi to build out my infrastructure as code setup, enjoying Pulumi’s quasi-imperative design on top of Terraform’s declarative approach. Common ways to control the infrastructure - like bringing dev down when I’m not utilizing it to save money, or controlling core platform or application specific infrastructure components are covered and don’t take much mental bandwidth now.

[feature/job_searcher !?   ](๑•ᴗ•)⊃━~━☆゚> just infra
🏗️   Infra Module
================
  🔧 infra-init                    - Initialize GCP credentials (first time setup)
  👀 platform-preview STACK=dev    - Preview platform changes (STACK required: dev|prod)
  🚀 platform-up STACK=dev         - Apply platform changes (STACK required: dev|prod)
  👀 app-preview STACK=dev         - Preview app changes (STACK required: dev|prod)
  🚀 app-up STACK=dev              - Apply app changes (STACK required: dev|prod)
  🔼 dev-up                        - Scale dev API/worker to 1
  🔽 dev-down                      - Scale dev API/worker to 0
  🔄 redeploy STACK=dev TYPE=all   - Force redeploy services (STACK required: dev|prod)
  📊 status STACK=dev              - Show service status (STACK required: dev|prod)
  🧾 stack-outputs STACK=dev       - Show stack outputs (STACK required: dev|prod)

🔐 Requires:
  - GCP credentials (run 'just infra infra-init' if missing)

HiredCoach on startup pulls down the latest configuration for its environment from its object store. I don’t just want to replace the file when I update a configuration - I want to track its versioning too!

⚙️   Config Module
================
  📥 get ENV          - Download active config.yml from GCS
  📤 set ENV FILE     - Upload file as active config (archives old)
  🔄 swap ENV VERSION - Swap to a specific historical version
  ⏮️ rollback ENV     - Roll back to previous version
  📋 list ENV         - List available versions

Examples:
  just config get ENV=dev
  just config set ENV=prod FILE=env/prod.yml
  just config rollback ENV=prod

Here I can request a given environment’s config file, set a new one, or rollback to one. When it uploads a new one, the same command goes ahead and marks the timestamp as the filename, uploading the file again under the new key. This way I can track when different configs became uploaded, and I can use the swap command to swap the environment back to an older version. This would normally take 8-9 commands or could be one of 50 randomly named scripts, but instead it’s natural for me to type just and work my way to the action I wanted to take; easy even when I’m coming back to the project after an extended absence.

…and to be frank, once you have the commands generally known (or the script prototyped) it’s really easy to just vibe code your way to a pretty sweet justfile command.

So yeah - just is nice. Try it out.