Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Monorepo vs Polyrepo: Trade-offs

By Kokil Thapa | Last reviewed: September 2026

Choosing between one repository and many is not a fashion decision. Monorepo vs Polyrepo: Trade-offs affect how fast you ship, how safely you deploy, and who owns what when a payment webhook breaks at midnight. On real client projects—Laravel booking apps, WooCommerce stores, legal-tech portals—I have seen both layouts work and both fail for predictable reasons. This guide compares them the way a working engineer needs: CI pipelines, deploy paths, package boundaries, and the boring ops details that decide whether your team moves fast or fights Git every week. For related architecture reading, see our CI/CD guide for monorepos with multiple PHP apps.

What is the difference between a monorepo and a polyrepo?

A monorepo stores multiple applications, packages, or services in one Git repository. A polyrepo splits each app, library, or service into its own repository. The code may look similar on disk. The operational story is completely different.

In a monorepo, a single pull request can update a shared validation package and every consumer in one atomic commit. In a polyrepo, that same change becomes a choreographed sequence: publish package, bump version, open pull requests in three other repos, and hope nobody merges out of order.

Repository Layout: Monorepo vs PolyrepoMonorepoSingle Git repositoryAdmin APILaravel 13Web appBlade + VueShared pkgComposer libPolyrepoMany Git reposIndependent lifecyclesRepo ARepo BPkg repoPkg repoVersion pins link repos
Monorepo vs polyrepo topology: one repo holds all apps and shared packages; polyrepo splits ownership per repository.

Neither layout is inherently cleaner. A monorepo can become a junk drawer without folder conventions. A polyrepo can become a version-pinning nightmare without a release discipline. The right question is which pain your team can afford.

Common monorepo folder shapes

Laravel teams often use a layout like this:

company-platform/
├── apps/
│   ├── admin-api/          # Laravel 13
│   ├── customer-portal/    # Laravel 13 + Livewire
│   └── marketing-site/     # WordPress 7.1 or static
├── packages/
│   ├── billing-sdk/        # Shared PHP library
│   └── domain-events/      # Internal event contracts
├── composer.json           # Root workspace (optional)
└── .gitlab-ci.yml          # Path-filtered pipelines

Polyrepo splits each path under apps/ and packages/ into separate remotes. Shared code ships through Composer (private Satis, GitLab package registry, or Packagist).

When should you choose a monorepo over a polyrepo?

Pick a monorepo when cross-cutting changes are frequent and your team is small enough to share one codebase culture. That pattern fits tightly coupled products: admin panel plus public API plus shared domain models.

I have maintained sister legal-tech sites that share deployment patterns but live in separate repos. That works because each site deploys independently and shares little runtime code. A booking platform with a supplier CRM, customer portal, and shared payment module is a different story. There, a monorepo often pays for itself within months.

  • One team owns the full product surface.
  • Shared PHP packages change weekly, not yearly.
  • You need atomic refactors across apps (rename a column, update every consumer).
  • CI can run path-filtered jobs so you do not test everything on every commit.
  • You want one issue tracker context: one PR shows the full feature.

Tools like Turborepo and Nx exist because monorepos without build graph awareness get slow. PHP monorepos lean on Composer path repositories, shared CI templates, and selective test runners instead.

How does CI/CD differ between monorepo and polyrepo setups?

CI is where Monorepo vs Polyrepo trade-offs stop being abstract. A polyrepo pipeline is simple: one repo, one pipeline, one deploy target. A monorepo pipeline must answer a harder question on every push: what actually changed?

Without path filters, a typo in a README triggers full test suites for five Laravel apps. That burns GitLab runner minutes fast. On budget-sensitive Nepal client projects, that cost shows up quickly—often Rs 3,000–8,000/month (~USD 22–60) in CI alone if you are careless.

CI/CD: Path Filters vs Single PipelineMonorepo CIGit push to mainDetect changed pathsTest App ALaravel onlyTest App BIf touchedDeploy changed appsPolyrepo CIPush to one repoFull pipeline runsTest + build + deployOne target server
Monorepo CI relies on path detection to avoid testing every app; polyrepo CI stays linear per repository.

GitLab CI path rules for a PHP monorepo

This pattern mirrors what I use on Deployer 7 pipelines for multi-app platforms. It runs jobs only when relevant folders change:

test-admin-api:
  rules:
    - changes:
        - apps/admin-api//*
        - packages/billing-sdk//*
  script:
    - cd apps/admin-api
    - composer install --no-interaction
    - php artisan test

deploy-admin-api:
  needs: [test-admin-api]
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      changes:
        - apps/admin-api/**/*
  script:
    - dep deploy admin-api -vvv

Polyrepo pipelines skip the changes: block entirely. The trade-off appears downstream: updating a shared Composer package means tagging a release, then bumping dependencies in each consumer repo. Miss one repo and production drift begins.

For deeper pipeline design, read our monorepo CI/CD walkthrough for PHP apps and compare it with infrastructure-as-code trade-offs when deploy targets multiply.

What are the main Monorepo vs Polyrepo trade-offs for Laravel and PHP teams?

The table below is the comparison most architects actually need. Scores are practical, not academic. Your context shifts them.

CriteriaMonorepoPolyrepo
Cross-app refactor speedExcellent — one PR, one reviewSlow — coordinated releases across repos
Deploy blast radiusHigher if CI filters failLower — each repo deploys alone
CI complexityHigh — path rules, caching, graph toolsLow — one pipeline per repo
Access controlHarder — repo-wide read accessEasier — per-repo permissions
Shared package workflowPath repos, instant local changesComposer tags, semver discipline
OnboardingOne clone, steep folder learning curveMany clones, simpler per-repo scope
Tooling fit (PHP/Laravel)Good with Composer workspacesNatural default for most agencies
Best team sizeSmall to mid, single product unitMid to large, separate squads

Laravel 13 needs PHP 8.3 or higher. Laravel 12 runs on PHP 8.2 through 2027. Your repo layout does not change those requirements. It changes how painful a framework upgrade becomes. Upgrading Laravel across four polyrepos without a shared baseline is four separate migration projects. In a monorepo, you see every composer.json diff in one place.

Similar boundary questions appear in Laravel multi-tenancy trade-offs and GraphQL vs REST trade-offs—the theme is always where you draw the line between shared and isolated.

Composer path repositories in a monorepo

Link internal packages without publishing:

{
  "repositories": [
    {
      "type": "path",
      "url": "../../packages/billing-sdk",
      "options": { "symlink": true }
    }
  ],
  "require": {
    "company/billing-sdk": "*"
  }
}

Polyrepo consumers instead require a semver constraint against a private registry. That is cleaner for third-party boundaries. It is slower for daily iteration.

How do deployment and production ops change in each model?

Deployment is the trade-off engineers feel at 11 p.m. I run Deployer 7 with symlinked releases on Ubuntu 24 servers for multiple client platforms. The repo layout directly affects rollback stories.

In a polyrepo, rolling back App A never touches App B. Each deploy.php targets one hostname. In a monorepo, one mistaken deploy task can push the wrong release path if your Deployer stages share a server tree. I have seen stale cron entries point at old release paths after a monorepo restructure. That class of bug is rare in polyrepos because paths stay stable per project.

Deployer 7: Release Paths per AppMonorepo checkout on build serverdep deployadmin-api stagedep deployportal stagedep deploymarketing stagecurrent → release/var/www/admincurrent → release/var/www/portalcurrent → release/var/www/marketingGotcha: shared cron must track each current symlink
Monorepo deploys with Deployer 7 need separate stages and symlink paths per app—cron and queue workers must follow each current release.

Production checklist that differs by layout

  1. Map every deploy stage to exactly one public hostname and one PHP-FPM pool.
  2. Store shared .env secrets outside release folders in both models.
  3. Reload PHP-FPM after symlink swap so opcache picks up changed files.
  4. In monorepos, namespace queue workers and cron entries per app path.
  5. In polyrepos, document cross-service API version contracts explicitly.
  6. Automate off-site backups per database, not per repo layout—see off-site backup automation.

Server work overlaps with Linux system administration and ongoing support contracts. Repo choice does not remove that ops layer.

How should agencies and product teams decide which model fits?

Decision criteria beat ideology. Start with release coupling, not Git aesthetics.

Choose polyrepo when squads are independent, clients own separate codebases, or compliance demands strict repository access walls. A lawyer directory platform and a trek booking system should not share a repo just because one agency built both. They share an agency, not a runtime.

Choose monorepo when one product spans multiple deployables that must move together. A Laravel customer portal, admin CRM, and shared payment module on one platform is the classic win—similar to how client portals with document sharing and payments benefit from unified domain logic.

Monorepo vs Polyrepo Decision TreeNew platform?YesNoShared codeweekly changes?Separateclients/products?MonorepoAtomic refactorsPolyrepoIsolated deploysHybrid: monorepo for productpolyrepo per client site
Decision tree for Monorepo vs Polyrepo trade-offs: shared weekly code changes favour monorepo; separate clients favour polyrepo.

Hybrid patterns that work in 2026

Most agencies land on a hybrid. Product platforms live in a monorepo. Client brochure sites and WordPress 7.1 installs stay in polyrepos. WooCommerce 11.1 shops rarely belong inside an enterprise monorepo unless the same team owns custom plugins tied to a Laravel backend.

Enterprise buyers evaluating layout should involve enterprise application development planning early. Wrong repo boundaries cost more to fix than server sizing mistakes.

For API-heavy splits, pair this decision with API development practices and rate limiting guidance. Polyrepo service meshes need explicit contracts. Monorepo services still need HTTP boundaries—folder proximity is not an excuse to skip validation.

Migration path: polyrepo to monorepo without a big bang

Teams rarely rewrite history cleanly. A sane incremental path:

  1. Create a monorepo with git subtree or filtered imports preserving blame where possible.
  2. Move shared libraries to packages/ with Composer path repos first.
  3. Unify CI with path filters before merging deploy pipelines.
  4. Keep separate Deployer stages until one release breaks—then fix deliberately.
  5. Document ownership in CODEOWNERS so monorepo scale does not erase accountability.

Ansible and server provisioning stay layout-agnostic. Your playbooks target hosts, not folders—see Ansible playbooks for PHP servers for the baseline either way.

Key Takeaways

  • Monorepo vs Polyrepo trade-offs hinge on release coupling, not Git preference—shared weekly code favours monorepo; independent products favour polyrepo.
  • Monorepos need path-filtered CI and clear folder conventions or runner costs and deploy risk spike fast.
  • Polyrepos simplify permissions and deploy isolation but multiply version coordination work across Composer packages.
  • Laravel and PHP teams should use Composer path repos in monorepos and private registry semver in polyrepos.
  • Deployer 7 stages, cron paths, and PHP-FPM reload discipline matter more after consolidating repos.
  • Hybrid layouts—monorepo for product, polyrepo for client sites—match most agency realities in 2026.

People Also Ask

Is monorepo better for small teams?

Usually yes, for one product with multiple apps. Small teams gain atomic changes and a single review surface. The monorepo becomes a liability when the team grows past clear ownership without CODEOWNERS and CI path filters.

Does Google use a monorepo because it is always superior?

No. Google’s monorepo fits their tooling, culture, and scale. Most PHP agencies lack Google's internal build graph infrastructure. Copy the pattern, not the assumption that one size fits all.

Can WordPress and Laravel live in the same monorepo?

Yes, but only when one team ships both on a coordinated release cycle. Otherwise WordPress 7.1 sites belong in separate repos with their own update cadence and plugin risk profile.

What tools help PHP monorepos besides Nx and Turborepo?

Composer path repositories, GitLab CI changes rules, PHPUnit with suite filters, Deployer multi-stage configs, and private Composer registries for packages that later extract to polyrepo consumers.

Pick the layout your deploy story can support

Monorepo vs Polyrepo trade-offs are really trade-offs about coordination cost versus isolation benefit. There is no trophy for picking the trendier layout. There is only whether your next cross-app change ships in one afternoon or three synchronized releases.

Audit your last five production incidents. If most involved version drift between repos, test a monorepo slice for shared packages. If most involved one bad deploy taking down unrelated apps, tighten polyrepo boundaries and CI filters. Validate JSON config shuffles during migration with our JSON formatter before they hit pipeline YAML.

Need help restructuring a Laravel platform, WooCommerce stack, or multi-app deploy pipeline? Review the portfolio for shipped examples, then contact us to plan a repo strategy that matches how your team actually releases software.

Frequently Asked Questions

A monorepo stores multiple applications, packages, or services in one Git repository. A polyrepo gives each app, library, or service its own repository. The code layout may look similar, but operations differ: one pull request can update shared code and every consumer atomically in a monorepo, while a polyrepo needs coordinated releases across separate repos.

Choose a monorepo when cross-cutting changes are frequent and one team owns a tightly coupled product—admin panel, public API, and shared domain models that must move together. It fits when shared PHP packages change weekly, you need atomic refactors across apps, and a single pull request should show the full feature. Small to mid teams on one product surface benefit most.

Pick polyrepo when squads are independent, clients own separate codebases, or compliance needs strict per-repository access. A lawyer directory and a trek booking system should not share a repo just because one agency built both. Polyrepo suits mid to large teams with separate release cycles, lower deploy blast radius, and simpler one-repo-one-pipeline CI.

Without path filters, a README typo can trigger full test suites for every app—often Rs 3,000–8,000/month (~USD 22–60) in GitLab runner minutes alone.

Polyrepo CI is linear: one repo, one pipeline, one deploy target. Monorepo CI must detect what changed on every push. Without path filters, unrelated edits burn runner minutes fast. GitLab CI changes rules run jobs only when relevant folders change—for example, testing admin-api only when apps/admin-api or packages/billing-sdk change. Polyrepo skips that complexity but adds version coordination when shared Composer packages update.

Monorepos excel at cross-app refactors in one PR but carry higher deploy blast radius if CI filters fail and harder repo-wide access control. Polyrepos isolate deploys and permissions but slow shared changes—you tag a release, bump each consumer, and risk production drift if one repo is missed. PHP monorepos use Composer path repos; polyrepos rely on semver via private registry. Framework upgrades hit four polyrepos as four separate migrations; a monorepo shows every composer.json diff together.

Path repositories link internal packages without publishing. In composer.json you point a repository type path at ../../packages/billing-sdk with symlink true, then require company/billing-sdk at any version. Local changes are instant. Polyrepo consumers instead require semver constraints against a private Satis, GitLab package registry, or Packagist—cleaner for third-party boundaries, slower for daily iteration.

In polyrepo, rolling back App A never touches App B—each deploy.php targets one hostname. Monorepo deploys need separate Deployer 7 stages and symlink paths per app on shared servers; a mistaken task can push the wrong release path. Stale cron entries pointing at old release paths after a restructure are a monorepo-specific risk. Map every stage to one hostname and one PHP-FPM pool, reload PHP-FPM after symlink swap, and namespace queue workers and cron entries per app path.

Polyrepo keeps blast radius lower—each repository deploys alone, so a bad deploy in one repo does not automatically affect others. Monorepo blast radius is higher if CI path filters fail or deploy stages are misconfigured, because multiple apps share one repository and potentially one server tree. Tightening Deployer stages and path-filtered pipelines reduces that risk.

Yes, but only when one team ships both on a coordinated release cycle. Otherwise WordPress 7.1 sites belong in separate repos with their own update cadence and plugin risk profile. Most agencies keep client WordPress installs in polyrepos unless custom plugins are tightly tied to a Laravel backend the same team maintains.

Usually yes, for one product with multiple apps. Small teams gain atomic changes and a single review surface without coordinating releases across repos. It becomes a liability when the team grows past clear ownership without CODEOWNERS files and CI path filters to limit test scope and assign accountability.

PHP monorepos lean on Composer path repositories for shared packages, GitLab CI changes rules for path-filtered pipelines, PHPUnit with suite filters for selective testing, Deployer 7 multi-stage configs for per-app deploys, and private Composer registries when packages later extract to polyrepo consumers. Build graph tools like Nx and Turborepo exist because monorepos without change awareness get slow.

Most agencies land hybrid: product platforms in a monorepo, client brochure sites and WordPress 7.1 installs in polyrepos. WooCommerce 11.1 shops rarely belong inside an enterprise monorepo unless the same team owns custom plugins tied to a Laravel backend. Shared weekly code changes favour monorepo; separate clients favour polyrepo regardless of who built them.

Create a monorepo with git subtree or filtered imports preserving blame where possible. Move shared libraries to packages/ with Composer path repos first. Unify CI with path filters before merging deploy pipelines. Keep separate Deployer stages until one release breaks, then fix deliberately. Document ownership in CODEOWNERS so scale does not erase accountability. Ansible playbooks stay layout-agnostic throughout.

No. Google's monorepo fits their internal tooling, culture, and scale. Most PHP agencies lack that build graph infrastructure. Copy the coordination pattern when release coupling warrants it—not the assumption that one layout wins everywhere. Audit recent production incidents: version drift between repos suggests monorepo; one bad deploy affecting unrelated apps suggests tighter polyrepo boundaries and CI filters.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: