# How Openship Detects Monorepos and Performs Selective Rebuilds on Push

> Openship automatically detects monorepos by scanning config files and selectively rebuilds changed services on Git push. Optimize your CI/CD pipeline today.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: deep-dive
- Published: 2026-07-30

---

**Openship automatically detects monorepo projects by scanning workspace configuration files and only rebuilds the specific services that changed when a Git push arrives.**

Managing large monorepos requires intelligent build systems that avoid redundant work. In the `oblien/openship` repository, the platform implements a three-stage pipeline that detects monorepo structures, calculates precise change sets from Git webhooks, and executes selective rebuilds. This architecture ensures that only modified sub-applications trigger new deployments, dramatically reducing CI time and resource consumption.

## How Monorepo Detection Works

Openship identifies monorepo projects during the initial configuration parsing phase by looking for specific workspace markers across multiple language ecosystems.

### Workspace Detector Registry

The detection logic starts in [`packages/core/src/workspaces/types.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/workspaces/types.ts), which maintains a registry of workspace detectors. Each detector maps a language or toolchain to a function that returns a boolean indicating whether the repository uses that workspace system.

The scanner checks for the following configuration files to determine the monorepo type:

- **JavaScript/TypeScript**: [`pnpm-workspace.yaml`](https://github.com/oblien/openship/blob/main/pnpm-workspace.yaml), [`lerna.json`](https://github.com/oblien/openship/blob/main/lerna.json), or Yarn workspace settings in [`package.json`](https://github.com/oblien/openship/blob/main/package.json)
- **Go**: `go.work` files
- **Rust**: `cargo` workspace definitions

When a push lands, the system initializes the appropriate detector based on the repository contents.

### Parsing the Monorepo Configuration

The `parseMonorepo()` function in [`packages/core/src/openship-config/parse.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/openship-config/parse.ts) (line 402) constructs an `OpenshipMonorepo` object containing:

- The list of sub-applications (`monorepoApps`)
- The root directory path
- Shared-path globs (`monorepoSharedPaths`) that apply to multiple services

This object becomes the source of truth for all subsequent build decisions.

## Calculating the Change Set on Push

When GitHub delivers a push webhook, Openship calculates exactly which services need rebuilding by comparing changed files against the monorepo structure.

### Webhook Processing and File Changes

Every push triggers a webhook stored in the `webhook_delivery` table defined in [`packages/db/src/schema/webhook-delivery.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/webhook-delivery.ts). The webhook handler in [`packages/dashboard/src/lib/api/deploy.ts`](https://github.com/oblien/openship/blob/main/packages/dashboard/src/lib/api/deploy.ts) extracts the list of changed files from the payload.

The system then applies two rules to determine impact:

1. **Direct path matching**: Files within a sub-application's directory trigger that specific service
2. **Shared path matching**: Files matching `monorepoSharedPaths` globs trigger all dependent services

### Shared Path Matching Logic

The `serviceKind` helper in [`packages/dashboard/src/lib/api/services.ts`](https://github.com/oblien/openship/blob/main/packages/dashboard/src/lib/api/services.ts) (line 15) and the deployment planner use the `monorepoSharedPaths` configuration defined in [`packages/db/src/schema/project.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/project.ts) (line 260) to classify changes.

If a changed file matches a sub-app's defined `path` **or** matches a shared glob pattern, that sub-app is added to the *changed-apps* set. Files outside these boundaries are ignored, preventing unnecessary builds.

## The Selective Rebuild Pipeline

Once the change set is determined, the build pipeline optimizes resource usage by rebuilding only the flagged services while reusing shared preparation steps.

### Build Pipeline Orchestration

The [`packages/adapters/src/runtime/build-pipeline.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/runtime/build-pipeline.ts) (line 304) receives the filtered list of changed applications. For each entry, it constructs a Docker image or bare-mode binary. Unchanged applications retain their existing images, eliminating redundant compilation.

### Docker Build Context Reuse

The [`docker-build-context.ts`](https://github.com/oblien/openship/blob/main/docker-build-context.ts) module (line 288) implements a critical optimization: it executes the *prepare-source-tree* step exactly once for the entire monorepo. After preparation, it runs each changed app's build in its own isolated context. This approach avoids the overhead of re-cloning or re-installing dependencies for every sub-service.

### Service Routing Updates

After successful builds, [`service-routing.ts`](https://github.com/oblien/openship/blob/main/service-routing.ts) updates only the reverse-proxy entries for rebuilt services. Unchanged routes remain untouched, ensuring zero-downtime deployments for stable services while new traffic routes to updated containers.

## End-to-End Implementation Example

The following TypeScript demonstrates the core detection and filtering logic:

```typescript
// Detecting a monorepo workspace
import { detectWorkspace } from '@openship/core/workspaces';

const isMonorepo = detectWorkspace(repoRoot, 'pnpm'); 
// Returns true if pnpm-workspace.yaml is present

// Computing changed applications in a webhook handler
import { getChangedFiles } from '@openship/api/webhooks';
import { matchSharedPaths } from '@openship/db/project';

const changed = getChangedFiles(payload);
const appsToBuild = monorepoApps.filter(app =>
  changed.some(file => 
    file.startsWith(app.path) || 
    matchSharedPaths(file, project.monorepoSharedPaths)
  )
);

```

You can also trigger selective rebuilds via CLI:

```bash
openship deploy \
  --project my-monorepo \
  --only $(git diff --name-only HEAD~1 HEAD | grep '^packages/web/')

```

## Summary

- **Monorepo detection** relies on a registry in [`packages/core/src/workspaces/types.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/workspaces/types.ts) that scans for workspace configuration files across ecosystems like pnpm, Lerna, Go workspaces, and Cargo.
- **Change calculation** uses `monorepoSharedPaths` globs from [`packages/db/src/schema/project.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/project.ts) to determine whether a file change affects a specific sub-app or the shared infrastructure.
- **Selective rebuilding** is orchestrated by [`packages/adapters/src/runtime/build-pipeline.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/runtime/build-pipeline.ts), which rebuilds only changed services while [`docker-build-context.ts`](https://github.com/oblien/openship/blob/main/docker-build-context.ts) reuses the prepare step across all builds.
- **Routing isolation** ensures that only updated services receive new proxy entries, leaving stable services untouched during deployment.

## Frequently Asked Questions

### How does Openship determine if a repository is a monorepo?

Openship checks for workspace configuration files such as [`pnpm-workspace.yaml`](https://github.com/oblien/openship/blob/main/pnpm-workspace.yaml), [`lerna.json`](https://github.com/oblien/openship/blob/main/lerna.json), `go.work`, or `cargo` workspace definitions. The `detectWorkspace` function in [`packages/core/src/workspaces/types.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/workspaces/types.ts) returns a boolean based on the presence of these files, and `parseMonorepo()` in [`packages/core/src/openship-config/parse.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/openship-config/parse.ts) constructs the `OpenshipMonorepo` object containing all sub-applications.

### What happens when a file in a shared directory changes?

When a changed file matches the `monorepoSharedPaths` glob patterns defined in the project schema, Openship flags all dependent sub-applications for rebuilding. This ensures that updates to shared libraries or configuration files trigger builds for every service that relies on them, maintaining consistency across the deployment.

### Does Openship support all package managers for monorepo detection?

Openship supports the major JavaScript package managers including pnpm, Yarn, and npm (via Lerna), as well as Go modules and Rust Cargo workspaces. The detector registry in [`packages/core/src/workspaces/types.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/workspaces/types.ts) maps each tool to its specific detection logic, making it extensible for additional ecosystems.

### How does selective rebuilding reduce resource consumption?

By parsing the Git webhook payload to identify exact file changes and matching them against sub-application paths, Openship builds only the Docker images or binaries for services that actually changed. The [`docker-build-context.ts`](https://github.com/oblien/openship/blob/main/docker-build-context.ts) module further optimizes this by running the expensive *prepare-source-tree* step once per monorepo rather than once per service, significantly reducing CPU time and storage usage.