Multi-Instance Development

Run multiple dev instances in parallel β€” useful for git worktrees, parallel coding agents, or working on multiple features simultaneously.

Quick Start

Just run bb dev β€” if any default port is already in use (e.g. by another project), the system automatically detects the conflict and picks free alternatives.

For stable, repeatable port assignments (worktrees, parallel agents), use prepare-instance:

# Set up custom ports for this worktree/checkout
bb prepare-instance 42

# Start as usual
bb dev

How It Works

The system has three layers working together:

1. bb prepare-instance β€” Port Configuration

Writes port overrides into deps.local.edn (gitignored) under :dev/options. Uses a numeric seed (ticket number, branch name hash, or explicit integer) to derive a stable, unique 6-port block per instance.

2. bin/launchpad β€” Port Injection at Startup

Before the REPL starts, reads :dev/options from the merged deps.edn + deps.local.edn and injects port overrides as environment variables that flow into every child process:

Env Var
Used By
Default

DEV_PG_PORT

docker-compose.yml (postgres host port)

5432

POSTGRES_PORT

resources/.secrets.edn (app DB connection)

5432

DEV_SHADOW_NREPL_PORT

shadow-cljs.edn

7002

DEV_SHADOW_HTTP_APP_PORT

shadow-cljs.edn

8081

DEV_SHADOW_HTTP_PORTFOLIO_PORT

shadow-cljs.edn

8082

PORT

resources/system.edn (http-kit server)

8080

3. Docker Compose Isolation β€” Three Modes

The :docker-compose key in :dev/options controls startup behaviour:

Value
Behaviour

:auto (default)

TCP-probe postgres on the configured port. If reachable β†’ skip docker compose. If not β†’ start it.

true

Always run docker compose up.

false

Never run docker compose up (manage services externally).

When not using --docker-override, prepare-instance also writes :docker-project-name (the main repo name) into :dev/options. This ensures that if a worktree has to start docker compose itself, it uses the same compose project as the main checkout β€” and therefore shares the same postgres_data volume. No data divergence between instances.

Default Ports (Single Instance)

Port
Service
Config Source

5432

PostgreSQL (β†’ 5432)

docker-compose.yml

7002

Shadow-CLJS nREPL

shadow-cljs.edn

7888

Clojure nREPL (Launchpad)

bin/launchpad

8080

HTTP Server (http-kit)

resources/system.edn

8081

Shadow dev HTTP (app)

shadow-cljs.edn

8082

Shadow dev HTTP (portfolio)

shadow-cljs.edn

Usage

bb prepare-instance [options] [seed]

Computes a unique port block and writes to deps.local.edn.

The seed can be:

  • A number: 42, 1234

  • A ticket ID: SC-1234, PROJ-42-my-feature

  • A branch name: my-feature (uses string hash for stable port assignment)

  • Omitted: uses the current git branch name

Options

Flag
Description

--branch <name>

Branch to use for this instance. Checks origin first; fetches locally if remote-only; creates from HEAD if new.

--worktree

Create a git worktree for the instance. Requires --branch.

--docker-override

Generate docker-compose.override.yml for a fully isolated postgres container (unique name, volume, network).

-h, --help

Show help

Examples

Worktree Directory

When --worktree is used, worktrees are created at:

Example: repo at ~/projects/shipclojure-pragma, branch feat/user-auth β†’

Override the base directory via deps.local.edn:

bb prepare-instance:worktree

Shorthand for bb prepare-instance --worktree. Prompts for a branch name if --branch is not provided.

bb list-instances

Shows all git worktrees and their configured ports.

Port Allocation

Offset
Service

+0

PostgreSQL host port

+1

nREPL

+2

Shadow-CLJS nREPL

+3

HTTP Server

+4

Shadow HTTP (app)

+5

Shadow HTTP (portfolio)

Example: seed 42 β†’ base 10042 β†’ ports 10042–10047.

Conflict resolution: before assigning ports the system checks all other worktrees' deps.local.edn and TCP-probes each port. If the preferred block is taken, it scans upward in steps of 6.

Configuration Details

deps.local.edn β€” Without --docker-override (shared postgres)

:docker-project-name ensures that if this worktree starts docker compose, it joins the main checkout's compose project and shares the same postgres data.

deps.local.edn β€” With --docker-override (isolated postgres)

docker-compose.override.yml β€” With --docker-override

Note: No port bindings in the override file. The postgres host port is controlled by DEV_PG_PORT in the base docker-compose.yml:

This avoids Docker Compose double-binding errors when merging base + override.

Running Tests in a Worktree

bb test automatically reads :postgres-port from deps.local.edn and forwards POSTGRES_PORT as an environment variable to the test process:

This works because the app reads the DB port from #or [#env POSTGRES_PORT 5432] in resources/.secrets.edn, and bb test injects this env var when a port override is configured.

Resetting to Defaults

Or manually edit deps.local.edn and remove the :dev/options key.

Data Flow

Last updated