Daniel Murillo
Index

Architecture · October 27, 2026 · Daniel Murillo

Deploys in monorepos: common problems and how to solve them

A monorepo does not force you to deploy everything together. How to detect impact, build artifacts, and publish only what you need.

A monorepo can hold many applications and libraries without requiring them to deploy together. The challenge is to build, test, and publish only what is needed while keeping project dependencies correct.

Introduction

A monorepo simplifies working with shared code, but it can complicate CI/CD if the pipeline treats the entire repository as a single application.

Imagine a repository with a web app, an API, an admin panel, and several internal libraries. A change in the panel should not force a rebuild and deploy of the API if there is no relationship that justifies it. Changing a shared library, however, can affect several consumers.

The most common problems are:

  • Every change triggers unnecessary builds and deploys.
  • An application does not include its internal dependencies when building the image.
  • The Docker build loses cache because it copies repository files too early.
  • CI filters do not detect changes in shared libraries.
  • An application is published without rebuilding the projects it depends on.
  • The artifact that was tested is not exactly the one that gets deployed.
  • Environment variables, migrations, and secrets are configured inconsistently across services.

The solution is not to automatically split the repository. It is to design a pipeline that understands dependencies, produces reproducible artifacts, and keeps deployment cycles independent.

1. First: CI is not the same as CD

CI (Continuous Integration) validates the change: installs dependencies, runs lint, tests, builds, and other checks.

CD (Continuous Delivery/Deployment) prepares and delivers artifacts, publishes images or packages, and deploys them to an environment.

In a monorepo it helps to distinguish at least three decisions:

  1. Which projects must be validated for a change.
  2. Which artifacts must be built and with which dependencies.
  3. Which applications must be deployed and to which environments.

The three sets are not always identical. A shared library may need tests and builds for several consumers, but the final deploy might be limited to the API that embeds it. If the library is published as an independent package, its own publication can also be a separate step.

2. Problem: every change runs the entire pipeline

A naive setup runs lint, tests, build, and deploy for every application on every push.

Change in apps/admin
        |
        v
Build web + API + admin
        |
        v
Deploy web + API + admin

This is simple, but it wastes time, CI minutes, and infrastructure resources. The problem becomes more obvious as the number of projects grows.

Solution: detect affected projects

The pipeline must know the relationships between projects and run the tasks relevant to the change.

With Nx, for example:

nx affected -t lint test build

Nx uses the project graph and a Git comparison base to determine which projects are affected. In CI, it is important that the checkout and base configuration allow commits to be compared correctly.

The official documentation explains the CI flow and the use of nx affected in Setting Up CI.

If you only use Bun or pnpm workspaces, you can start with package filters and path rules. Path rules alone, however, do not necessarily understand the full dependency graph. If apps/api depends on packages/contracts, a change in contracts must trigger the relevant API validations.

Important rule: CI filters must respect the dependency graph, not only the modified folder.

3. Problem: an application depends on internal packages

An application may compile correctly at the monorepo root and fail when we try to deploy only its folder.

Example:

repo/
├── apps/
│   └── api/
└── packages/
    ├── contracts/
    └── utils/

The API imports @acme/contracts and @acme/utils. If we copy only apps/api into a Docker image, those packages may not be present.

Solution A: build from the root

The Docker context must include the required internal dependencies. For example:

docker build -f apps/api/Dockerfile .

The trailing dot means the repository root is the build context. The Dockerfile must copy and install the files needed to resolve the workspaces.

This is a direct strategy, but avoid copying the entire repository indiscriminately before installing dependencies, because that can invalidate the Docker cache.

Solution B: generate an isolated artifact

Another option is to create a deploy directory that contains the application and the dependencies it needs.

With pnpm, pnpm deploy can generate a portable directory with the package’s production dependencies, including the corresponding workspace dependencies:

pnpm --filter=@acme/api --prod deploy ./deploy/api

Check the pnpm version and workspace configuration, because the details of deploy have evolved. Documentation: pnpm deploy.

With Turborepo, turbo prune generates a reduced version of the monorepo with the code and lockfile relevant to an application:

turbo prune api --docker

turbo prune and pnpm deploy do not produce exactly the same thing: one generates a partial monorepo to build the application; the other prepares an isolated deploy package. The choice depends on the build and runtime flow.

References:

4. Problem: Docker invalidates the cache too often

A Dockerfile that copies the entire repository before installing dependencies can cause an unrelated change to invalidate the install layer.

COPY . .
RUN pnpm install

If the web app is modified, the API’s install layer may rebuild even though its dependencies have not changed.

Solution: separate manifests and source code

The general idea is to copy the required manifests and lockfile first, install dependencies, and copy the code afterward.

# Conceptual example: the COPY steps must include every
# package.json and workspace file required by the app.
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/api/package.json apps/api/package.json
COPY packages/contracts/package.json packages/contracts/package.json

RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm --filter=@acme/api build

This example illustrates the principle; in a real monorepo, the package.json files of every required internal dependency must also be present, and the COPY layout must respect the workspace.

For large repositories, pruning can simplify the process because it reduces the files that participate in the build. The Turborepo guide explains how to separate manifests and sources to improve Docker cache reuse: Docker guide.

5. Problem: deploying only the modified project breaks dependencies

Suppose apps/api depends on packages/contracts. A change in contracts can affect the API even though no file in apps/api has changed.

If the pipeline deploys only when the apps/api folder changes, it can leave production with an artifact that does not match the current code, or skip the necessary tests.

Solution: define the impact policy

For each project, define:

  • Which internal packages it consumes.
  • Which tasks must run when those dependencies change.
  • Which artifacts must be rebuilt.
  • Which applications must be redeployed.
  • Which changes require validating the entire repository.

A types library may require tests and rebuilds of its consumers. A documentation-only change may not need a deploy. A change to root configuration or the lockfile can affect many projects and deserves an explicit policy.

Not every monorepo change has the same scope.

6. Problem: the pipeline misdetects Git changes

Tools that compute affected projects need a correct comparison reference. If CI gets a shallow checkout or compares against the wrong commit, it may not detect the expected changes.

Solution

  • Fetch the history or references the tool needs.
  • Correctly define the base branch for pull requests.
  • On pushes to the main branch, compare against the appropriate base commit for the CI provider.
  • Test the pipeline with changes in an application, a shared library, root configuration, and the lockfile.
  • Review the affected-projects logs when the result is unexpected.

Nx documents the importance of the Git base and of configuring the checkout for nx affected in Setting Up CI.

7. Problem: reproducible builds and inconsistent artifacts

A common risk is building an image in CI, testing one version, and then rebuilding at deploy time. If the environment, dependencies, or inputs change, what is deployed may not be identical to what was validated.

Solution: build once and promote the artifact

A safer pipeline often follows this flow:

Commit / Pull Request
        |
        v
Lint + Tests + Build
        |
        v
Create image with immutable tag
        |
        v
Scan and validate artifact
        |
        v
Promote the same image
        |
        v
Staging -> Production

Use lockfiles, reproducible installs, immutable image tags or digests, and a clear promotion policy between environments. Avoid relying on latest as the identifier of a version that must be reproducible.

8. Problem: mixed configuration and secrets

Applications in a monorepo can have different environment variables, different permissions, and independent deployment cycles. Sharing code does not mean sharing secrets.

Solution

  • Keep secrets out of the repository.
  • Manage variables per application and environment.
  • Apply least privilege to each service.
  • Validate required variables at startup.
  • Prevent a shared configuration from introducing accidental dependencies between applications.
  • Document the differences between development, staging, and production.

A shared configuration library can centralize validations and conventions, but it should not include real credentials or assume that every service uses the same variables.

9. Problem: database migrations and coordinated deploys

A new API version may depend on a schema change. In a monorepo, the API code and the migration can change in the same commit, but the deploy is not necessarily atomic.

Solution: compatible migrations

When possible, use an expand-and-contract strategy:

  1. Add the new field or structure in a compatible way.
  2. Deploy code that can work with both the old and the new schema.
  3. Migrate or backfill data.
  4. Switch consumers to the new schema.
  5. Remove old structures in a later release, once they are no longer used.

Not every migration can be done without coordination, but designing version compatibility reduces the risk of deploying services at different times.

10. A recommended pipeline for monorepos

A practical strategy can be split into these phases:

Pull request

  • Install dependencies with a reproducible lockfile.
  • Detect affected projects.
  • Run the necessary lint, tests, and builds.
  • Run integration tests when contracts or critical dependencies change.
  • Do not deploy production from every pull request unless there is an explicit flow for ephemeral environments.

Merge to the main branch

  • Recalculate the change scope.
  • Build artifacts for the affected applications.
  • Create immutable images.
  • Run scans and validations.
  • Publish artifacts to a registry.

Deploy

  • Deploy only the services whose artifact changed or needs updating.
  • Apply migrations safely.
  • Run smoke tests and health checks.
  • Promote the same artifact across environments.
  • Have a rollback or roll-forward strategy.

The exact design depends on the CI/CD provider, the runtime, and the deployment platform. GitHub Actions, GitLab CI, Azure DevOps, Jenkins, and other systems can implement these principles.

11. Do I need Nx or Turborepo to solve this?

Not always.

ToolWhat it providesWhen it is enough
Bun or pnpm WorkspacesPackages and dependenciesSmall monorepo with simple scripts
NxGraph, affected, and task cacheExpensive CI and many related projects
TurborepoOrchestration and turbo prune for DockerYou want to reduce the build context per app
pnpm deployPortable package with production depsIsolated deploy from a workspace

These tools overlap in some areas, but they are not identical. The choice should be based on the dependency graph, the build strategy, the artifact format, and the deployment platform.

For choosing Bun, pnpm, and Nx at the code-organization layer, see Monorepo: Bun, pnpm, and Nx.

12. An important rule: separate the repository from the deploy

Having a single repository does not force you to deploy everything together. You can have a single source of truth for the code and keep artifacts, images, variables, scaling, and release cycles separate.

The key is to distinguish:

UnitWhat it is
Code unitThe package or project inside the repository
Build unitSources and dependencies needed to produce an artifact
Deploy unitThe application or service that is published and operated independently

When these units are defined, the monorepo stops being a difficulty and becomes a way to coordinate changes without giving up independent deploys.

Conclusion

Deployment problems in monorepos usually come from pipelines that do not understand the dependency graph, builds that include too much context, artifacts that are not reproducible, or deployment policies that are too broad.

The solution is to detect the real impact of each change, build only what is needed, include all internal dependencies, use the cache correctly, and promote immutable artifacts.

A monorepo should not mean deploying everything together. It should let you change related code in one place and deploy each application with the independence it needs.

Start with a simple pipeline, define dependencies clearly, and add orchestration tools when complexity and CI cost justify them.

In this series

  1. Monorepo: Bun, pnpm, and Nx
  2. Deploys in monorepos (this post)

References