Skip to content

Just Modules

Recipes are organised into just modules declared in Justfile.tmpl. For the what-belongs-where split between daily-use recipes and maintainer recipes, see Dotfiles vs Maint.

Discovery

Every module inherits common recipes (list, show, usage, help). The list recipe accepts variadic *submodules, so it absorbs space-separated module tokens that follow it. Combined with just's left-to-right module traversal, list can appear at any position:

just list docker compose        # list absorbs "docker compose" as submodules
just docker list compose        # docker's list absorbs "compose"
just docker compose list        # Traverse to docker::compose, run list
just docker::compose::list      # Fully qualified namepath
just docker::compose            # Module default (delegates to list)

Once just reaches a recipe (like list), remaining tokens are arguments — and :: in arguments is fine:

just list docker::compose       # "docker::compose" passed as a single submodule arg
just docker::list compose       # docker::list is a recipe, "compose" is an arg
just maint::list test::smoke    # maint::list is a recipe, "test::smoke" is an arg

Gotcha: just docker::compose list does not run docker::compose::list. Because docker::compose resolves to the module's default recipe (which accepts *submodules), the word list is absorbed as a submodule argument — building the path docker::compose::list, which doesn't exist. Use all spaces or all :::

just docker::compose list       # Fails — "list" becomes a submodule arg
just docker compose list        # Works — space traversal, then list recipe
just docker::compose::list      # Works — fully qualified recipe

Why the default is variadic

The default declares *submodules (variadic) so it absorbs every trailing token. If it took a fixed arity instead — say one submodule arg — excess tokens would chain as separate root-level recipe invocations: just docker::compose foo bar would consume foo and then attempt bar as a top-level recipe. The variadic prevents that, at the cost of the gotcha above.

Recipe Naming Conventions

Public Recipes

Pattern Meaning
debug-* Create container, run setup, then drop into a ZSH shell
*-local Apply configuration from your LOCAL source (dev mode)
*-repo Apply configuration from the GIT REMOTE (prod/CI mode)

Recipe Arguments

Argument Usage
distro Target OS: debian, fedora, or bluefin (default: debian)

Example: just maint test smoke debug-local bluefin

Private Helpers

Pattern Purpose
_require-<tool> Fail-fast guard. Checks that <tool> is installed and exits with a helpful error if not.
Thin wrappers around the generic _require recipe in _common.just.
_ensure-<tool> Self-healing bootstrapper. Installs or configures <tool> if missing, silently succeeds
if present. May depend on a _require-* guard.
_pick-<resource> Interactive fzf selector. Returns the user's selection to stdout.
Always depends on _require-fzf.

Style Guide

  • Group related recipes using [group('name')]
  • Document recipes with comments above them
  • Use private helpers (prefix with _) for internal logic
  • Prefer dependencies over just subprocess calls where possible

Module Help Files

Each just module can have a help.md file rendered by just <module> help. These are runtime help for the user — not architecture docs, not implementation details. When writing or editing module help:

  • Do not duplicate just --list — the one-line comment above each recipe is already available via just <module>. Help files should add context that the one-liner cannot.
  • Focus on practical runtime guidance — when to reach for a recipe, what to expect, what choices to make.
  • Include gotchas and non-obvious behaviour — e.g. "will fail if you haven't pushed", "Bluefin tests are slower", "requires confirmation".
  • Do not explain chezmoi/Docker/tool internals — that belongs in the architecture docs.