Skip to content
All essays
CraftMarch 27, 202512 min

Polyrepos: When to Use Multiple Repositories

Understand polyrepo architecture. Learn when to use multiple repositories and how to manage distributed codebases effectively.

Ü
Ümit Uz
Mobile & Full Stack Developer

Understanding Polyrepos

A polyrepo (or multi-repo) strategy involves storing each project or component in its own separate Git repository. This traditional approach offers autonomy and clear boundaries but requires coordination across repositories.

bash
# Polyrepo structure
github.com/myorg/
├── web-app/              # Frontend repository
├── api-service/          # Backend repository
├── mobile-app/           # Mobile repository
├── design-system/        # UI library repository
└── shared-utils/         # Utilities repository

When to Use Polyrepos

Ideal Use Cases

bash
# 1. Unrelated projects
- Website
- Mobile app
- Internal tools

# 2. Different teams
- Frontend team owns web-app
- Backend team owns api-service
- Mobile team owns mobile-app

# 3. Independent deployment
- Frontend deploys to Vercel
- API deploys to AWS
- Mobile deploys to App Store

# 4. Different tech stacks
- React frontend
- Python backend
- Swift mobile app

Polyrepo Advantages

Clear Boundaries

bash
# Each repo has:
✓ Clear ownership
✓ Independent versioning
✓ Separate CI/CD
✓ Focused codebase
✓ Faster git operations

Team Autonomy

bash
# Frontend team
- Owns web-app repo
- Makes decisions independently
- Deploys without coordination

# Backend team
- Owns api-service repo
- Sets own standards
- Releases independently

Managing Polyrepos

Repository Organization

bash
# GitHub organization structure
myorg/
├── web-frontend/         # React app
├── api-backend/          # Node.js API
├── mobile-ios/           # iOS app
├── mobile-android/       # Android app
├── design-system/        # Component library
├── shared-types/         # TypeScript types
└── docs/                 # Documentation

# Use consistent naming
- Prefix with platform (web-, api-, mobile-)
- Clear purpose from name
- Avoid ambiguity

Code Sharing Strategies

Published Packages

bash
# Create shared library
cd design-system/
npm init -y

# Build and publish
npm run build
npm publish

# Use in other repos
cd web-frontend/
npm install @myorg/design-system

Git Submodules

bash
# Add shared code as submodule
cd web-frontend/
git submodule add https://github.com/myorg/shared-utils.git lib/utils

# Update submodule
git submodule update --remote

Private npm Registry

bash
# Set up private registry
npm set registry https://npm.myorg.com

# Publish private packages
npm publish --registry=https://npm.myorg.com

# Install from private registry
npm install @myorg/shared-utils

Dependency Management

Version Pinning

json
// web-frontend/package.json
{
  "dependencies": {
    "@myorg/ui-components": "^2.1.0",
    "@myorg/api-client": "^1.5.2",
    "@myorg/types": "^3.0.0"
  }
}

// Use semantic versioning
// ^2.1.0 - Compatible with 2.x.x
// ~2.1.0 - Compatible with 2.1.x
// 2.1.0 - Exact version

Dependency Updates

bash
# Update dependencies regularly
npm update

# Check for outdated
npm outdated

# Use Renovate or Dependabot
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"

CI/CD Coordination

Independent Pipelines

yaml
# web-frontend/.github/workflows/deploy.yml
name: Deploy Frontend

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm test
      - run: npm run build
      - run: npx vercel deploy --prod

Orchestration

yaml
# orchestration-repo/.github/workflows/deploy-all.yml
name: Deploy All Services

on:
  workflow_dispatch:

jobs:
  deploy-frontend:
    uses: myorg/web-frontend/.github/workflows/deploy.yml@main

  deploy-backend:
    uses: myorg/api-backend/.github/workflows/deploy.yml@main

  deploy-mobile:
    uses: myorg/mobile-ios/.github/workflows/deploy.yml@main

Communication Between Services

API Contracts

typescript
// OpenAPI specification
// api-backend/openapi.yaml
openapi: 3.0.0
info:
  title: My API
  version: 1.0.0

paths:
  /users:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string

Shared Types

bash
# Publish types from shared-types repo
cd shared-types/
npm run build
npm publish

# Use in other repos
cd web-frontend/
npm install @myorg/types

# Now types are in sync
import { User } from '@myorg/types';

Common Challenges

Synchronization Issues

bash
# Problem: API changes break frontend
# Backend updates user object
# Frontend still using old fields

# Solution: API versioning
GET /api/v1/users
GET /api/v2/users

# Or use contract testing
Pact for consumer-driven contracts

Code Duplication

bash
# Problem: Same utility in multiple repos
# web-frontend/src/utils/format.ts
# api-backend/src/utils/format.ts

# Solution: Extract to shared library
cd shared-utils/
cp ../web-frontend/src/utils/format.ts ./src/
npm publish

# Use in both repos
npm install @myorg/utils

Version Conflicts

bash
# Problem: Different repos use different versions
# web-frontend uses react@18
# mobile-frontend uses react@17

# Solution: Standardize versions
# Use org-wide eslint rule
// .eslintrc.js
module.exports = {
  rules: {
    'no-restricted-imports': ['error', {
      patterns: [{
        group: ['react'],
        message: 'Use react@18 only'
      }]
    }]
  }
};

Best Practices

1. Clear Ownership

markdown
# Each repo README

## Ownership
- Maintainer: Frontend Team
- Contact: frontend-team@myorg.com
- On-call: @frontend-lead

## Support
- Issues: GitHub Issues
- Slack: #frontend-support

2. Documentation

markdown
# API documentation
# api-backend/README.md

## API Endpoints

### GET /users
Get all users

### POST /users
Create new user

See [docs.myorg.com/api](https://docs.myorg.com/api)

3. Consistent Standards

bash
# Shared linting config
cd eslint-config-myorg/
npm publish

# Use in all repos
npm install --save-dev eslint-config-myorg

# .eslintrc.js
module.exports = {
  extends: ['myorg']
};

4. Automate Cross-Repo Operations

bash
# Script to update all repos
#!/bin/bash
repos=("web-frontend" "api-backend" "mobile-ios")

for repo in "\${repos[@]}"; do
  cd ../\$repo
  git pull origin main
  npm update
  npm test
done

Tooling for Polyrepos

Multi-Repo Management

bash
# Google's Repo tool
# Manage multiple git repos

# .repo/manifest.xml
<manifest>
  <remote name="github" fetch="https://github.com/" />
  <project name="myorg/web-frontend" path="web" />
  <project name="myorg/api-backend" path="api" />
</manifest>

# Sync all repos
repo sync

GitHub Organization

bash
# Use GitHub features
- Teams for access control
- CODEOWNERS for approvals
- Protected branches
- Required checks
- Dependabot for updates

Monitoring Polyrepos

Centralized Monitoring

yaml
# observability-repo/docker-compose.yml
services:
  prometheus:
    image: prom/prometheus
  grafana:
    image: grafana/grafana
  loki:
    image: grafana/loki

# All repos send metrics here

Distributed Tracing

typescript
// Add tracing to all services
import * as tracing from '@opentelemetry/api';

// web-frontend/src/telemetry.js
const tracer = tracing.trace.getTracer('web-frontend');

// api-backend/src/telemetry.js
const tracer = tracing.trace.getTracer('api-backend');

// Trace requests across services

Migration: Monorepo to Polyrepo

Extract Repository

bash
# Split monorepo into separate repos
# Extract frontend
git subtree push --prefix=apps/web origin web-frontend

# Extract backend
git subtree push --prefix=apps/api origin api-backend

# Now in separate repos
cd web-frontend/
git remote set-url origin git@github.com:myorg/web-frontend.git

When Polyrepos Shine

Large Organizations

bash
# Google, Meta, Microsoft
- Thousands of repositories
- Independent teams
- Clear ownership
- Scalable approach

Open Source Projects

bash
# Separate repos for:
- Core library
- Plugins
- Documentation
- Examples
- CLI tools

Different Tech Stacks

bash
# Frontend: JavaScript/TypeScript
# Backend: Go/Rust
# Mobile: Swift/Kotlin
# ML: Python
# Each repo optimized for its stack

Comparison

Monorepo vs Polyrepo Decision Matrix

bash
# Use Monorepo when:
✓ Projects are closely related
✓ Frequent cross-project changes
✓ Shared dependencies
✓ Single team or small org
✓ Unified tooling

# Use Polyrepo when:
✓ Projects are independent
✓ Different teams/tech stacks
✓ Separate deployment cycles
✓ Large organization
✓ Clear boundaries needed

Conclusion

Polyrepos offer autonomy, clear boundaries, and independent deployment at the cost of coordination overhead. They're ideal for large organizations with multiple teams and diverse tech stacks.

Key takeaways:

  • Use polyrepos for independent projects
  • Establish clear ownership
  • Automate cross-repo operations
  • Share code via published packages
  • Maintain consistent standards

Choose polyrepos when autonomy and clear boundaries matter most!

Enjoyed this essay? Share it.

Related essays

Next essay
Tree Algorithms: Master Binary Trees and Beyond