Skip to content

Monorepos

Grove works well with monorepos — projects with multiple services, packages, or apps in a single repository.

The challenge

Monorepos typically run multiple services in parallel (web, api, worker, etc.). In a worktree-based workflow, you need each branch to have its own isolated set of services running on unique ports. Without coordination, services from different branches collide.

Configuration

Declare your services as providers in .grove/config.json:

json
{
  "project": "my-monorepo",
  "providers": {
    "web": { "type": "docker-compose", "service": "web" },
    "api": { "type": "docker-compose", "service": "api" },
    "worker": { "type": "docker-compose", "service": "worker" }
  },
  "shared": { "db": true, "redis": true },
  "naming": {
    "sharedProject": "my-monorepo-shared",
    "composeProject": "${project}-${branch_safe}",
    "dbSchema": "${project}_${branch_safe}",
    "ports": {
      "WEB_PORT": "auto",
      "API_PORT": "auto",
      "WORKER_PORT": "auto",
      "DB_PORT": "auto",
      "REDIS_PORT": "auto"
    }
  }
}

With this config, grove start feat/my-feature generates a .env.worktree like:

bash
COMPOSE_PROJECT_NAME=my-monorepo-feat-my-feature
WEB_PORT=8081
API_PORT=8082
WORKER_PORT=8083
DB_PORT=5433
REDIS_PORT=6380
DB_SCHEMA=my_monorepo_feat_my_feature
SHARED_PROJECT_NAME=my-monorepo-shared

Each worktree gets a different set of ports. No two branches conflict.

Compose file layout

compose.yaml           # all per-worktree services
compose.shared.yaml    # shared: db, redis

compose.yaml:

yaml
services:
  web:
    build: ./packages/web
    ports:
      - "${WEB_PORT}:3000"
    environment:
      API_URL: http://localhost:${API_PORT}

  api:
    build: ./packages/api
    ports:
      - "${API_PORT}:4000"
    environment:
      DATABASE_URL: postgres://postgres:dev@db:5432/${DB_SCHEMA}
      REDIS_URL: redis://redis:6379/${REDIS_DB:-0}

  worker:
    build: ./packages/worker
    ports:
      - "${WORKER_PORT}:5000"
    environment:
      DATABASE_URL: postgres://postgres:dev@db:5432/${DB_SCHEMA}

networks:
  shared_net:
    external: true
    name: ${SHARED_PROJECT_NAME}_default

Worktree root

By default, Grove places worktrees at <repo-parent>/<repo-name>-worktrees/. In a monorepo with a long path you might want to override this:

json
{
  "worktrees": {
    "root": "/fast-disk/worktrees/my-monorepo"
  }
}

Or use an environment variable for machine-specific overrides without touching the config:

bash
GROVE_WORKTREE_ROOT=/fast-disk/worktrees grove start feat/my-feature

Parallel development across workspaces

With multiple worktrees running:

bash
grove status
  branch                   provider        web              status
  feat/payments            docker-compose  :8081            running
  feat/notifications       docker-compose  :8084            running
  fix/api-race             docker-compose  :8087            stopped
  main                     docker-compose  —                —

Switch between contexts without stopping anything:

bash
grove open feat/payments
# ... work on payments ...
grove open feat/notifications
# ... work on notifications ...

Selective service startup

If your monorepo has services you don't always need, you can start only the ones relevant to a branch by filtering with docker compose:

bash
grove docker up feat/payments
# then selectively scale up only what you need:
docker compose -p my-monorepo-feat-payments --env-file .env.worktree up api db -d

Grove sets up the env — you can always use docker compose directly with the generated .env.worktree.

Released under the MIT License.