Skip to content

Environment setup

Once your first workspace is running, this page covers how to make Mooj work with your repo: lifecycle scripts you can hook into workspace creation and removal, environment variables Mooj exposes, and how to use ports without collisions.

A JSON file at the root of your main repo (not inside individual workspaces).

{
"setup": "bin/setup",
"run": "bin/run",
"archive": "bin/archive"
}

All three keys are optional, each holds a single shell command. Mooj runs them with the workspace as cwd and .moojenv already sourced. Keep the actual scripts under bin/ so they’re versioned with everything else.

Runs in the background after a new workspace is created. Install dependencies, copy env files into place, anything else a fresh checkout needs.

#!/usr/bin/env bash
set -euo pipefail
# Reuse the env file from the main repo
cp "$MOOJ_REPO_PATH/.env" .env
# Install dependencies
npm install
# bundle install # Ruby
# uv sync # Python (uv)
# cargo fetch # Rust
# Provision a workspace-scoped database, run migrations, etc.
# createdb "myapp_$MOOJ_WORKSPACE_NAME"
# bin/rails db:prepare

Runs when you click Run on a workspace. Conventionally your dev server. Bind to $MOOJ_PORT so each workspace’s server lands on its own port.

#!/usr/bin/env bash
set -euo pipefail
PORT="$MOOJ_PORT" npm run dev
# PORT="$MOOJ_PORT" bin/rails server # Rails
# overmind start -p "$MOOJ_PORT" # Procfile-based (overmind/foreman)
# docker compose up # docker compose (set ports via $MOOJ_PORT in compose)

Runs when you remove a workspace, before the worktree is deleted. Clean up anything you don’t want lingering on disk.

#!/usr/bin/env bash
set -euo pipefail
# Reclaim disk space
rm -rf node_modules dist .next
# Drop the workspace-scoped database created in setup
# dropdb --if-exists "myapp_$MOOJ_WORKSPACE_NAME"

Mooj writes a .moojenv file inside every workspace at ~/mooj/workspaces/<repo>/<codename>/.moojenv. It contains:

Terminal window
export MOOJ_WORKSPACE_PATH="…/<repo>/<codename>"
export MOOJ_WORKSPACE_NAME="<codename>"
export MOOJ_REPO_PATH="…/<main repo>"
export MOOJ_REPO_NAME="<repo>"
export MOOJ_PORT="<allocated port>"

Where these are visible:

  • Inside setup / run / archive: always. Mooj sources .moojenv itself before running each lifecycle script, so your scripts can rely on $MOOJ_PORT and friends with no extra setup.
  • Inside terminal sessions you open in the workspace: only if you have direnv installed (see next section).

Install direnv so the variables are in your environment when you open a terminal session in a workspace:

Terminal window
brew install direnv

Add the shell hook to your ~/.zshrc (or ~/.bashrc):

Terminal window
eval "$(direnv hook zsh)"

Open a new terminal and you’re set. Mooj runs direnv allow on every workspace it creates, so there’s no extra trust prompt.

If your repo already has a tracked .envrc (for shared direnv config), Mooj won’t overwrite it — append this line so direnv picks up Mooj’s variables too:

Terminal window
source_env_if_exists .moojenv

Without direnv, source .moojenv yourself when you need the variables.

Each workspace reserves a block of 10 consecutive ports. MOOJ_PORT is the first; the rest are MOOJ_PORT+1 through MOOJ_PORT+9. Use them in your dev server config so workspaces don’t collide.

Worked example — a web app reading PORT and an API reading API_PORT:

Terminal window
PORT="$MOOJ_PORT" npm run dev # web on 10010
API_PORT="$((MOOJ_PORT + 1))" bin/api # api on 10011

Mooj writes two files into every worktree: .moojenv and .envrc. Both are machine-local — paths and ports are specific to your machine — so they don’t belong in your repo. Add them to your .gitignore:

.moojenv
.envrc

Whether to share mooj.json and your lifecycle scripts is your call: track them in git to share with the team, or .gitignore them if they’re just for you.