Back to Blog
Guide12 min read2026-07-05

How to Set Up Monorepo Deployments

Deploy multiple services from one monorepo cleanly — per-service root directories, shared packages, build caching, and avoiding the trap of rebuilding everything on every push.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

# How to Set Up Monorepo Deployments

Monorepos are great for development — shared code, atomic cross-service changes, one place to look. They're notoriously annoying for deployment, because a naive setup rebuilds and redeploys *everything* on every push, even when you only touched one service. This guide shows how to set up clean monorepo deployments that build only what changed.

The core problem

A monorepo has multiple deployable units in one repository:

my-monorepo/
├── apps/
│   ├── web/          # Next.js frontend
│   ├── api/          # Express backend
│   └── worker/       # background worker
├── packages/
│   └── shared/       # shared TypeScript lib
├── package.json
└── pnpm-workspace.yaml

Deployment platforms historically assumed one repo = one app. The keys to making a monorepo work are: per-service root directories, shared-package resolution at build time, and change detection so you don't rebuild untouched services.

Decision 1: per-service root directory

Each deployable app needs its own "root" so the platform knows where its package.json, Dockerfile, and build/start commands live. On PandaStack, you create a separate app per service and point each at its subdirectory (e.g. apps/web, apps/api, apps/worker). All three connect to the *same* GitHub repo but with different root directories.

apps/web    → static site (or container)
apps/api    → container app, managed DB auto-wired
apps/worker → container app (worker command)

Decision 2: resolving shared packages

The trap with subdirectory roots: apps/api imports packages/shared, which lives *outside* apps/api. If the build context is only apps/api, the import fails. Two clean solutions:

A. Build from the repo root with a service-specific Dockerfile. Set the build context to the repo root so shared packages are available, and target the service in the Dockerfile:

# apps/api/Dockerfile — built with context = repo root
FROM node:20-slim
WORKDIR /repo
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages ./packages
COPY apps/api ./apps/api
RUN corepack enable && pnpm install --filter ./apps/api...
RUN pnpm --filter ./apps/api build
WORKDIR /repo/apps/api
CMD ["node", "dist/index.js"]

The --filter ./apps/api... syntax (pnpm) installs and builds the API *and its workspace dependencies*, skipping unrelated apps.

B. Prebuild shared packages and reference built artifacts. Build packages/shared first, then consume its compiled output. Works but adds ordering complexity; the filtered Docker build above is usually cleaner.

Decision 3: only build what changed

This is the efficiency win. You don't want a docs typo in apps/web to redeploy apps/api. Approaches:

  • Path-based triggers: configure each app to deploy only when files under its path (or its dependencies' paths) change.
  • Monorepo tooling: Turborepo or Nx compute the affected projects from the dependency graph and let you build only those. turbo run build --filter='...[origin/main]' builds only what a diff touched.

Using a tool like Turborepo also gives you remote build caching — unchanged packages are restored from cache instead of rebuilt, dramatically cutting build minutes.

Deploying a monorepo on PandaStack

  1. 1Push the monorepo to GitHub.
  2. 2For each service, create an app in the [dashboard](https://dashboard.pandastack.io), connect the same repo, and set the root directory (and build context where shared packages are involved).
  3. 3Set build/install/start commands per app. PandaStack auto-detects frameworks and lets you override the install command (npm/yarn/pnpm/bun) — important for pnpm/yarn workspaces.
  4. 4Provision a managed database once and let DATABASE_URL auto-wire into the services that need it (e.g. api, worker) — shared cleanly across the monorepo's services.
  5. 5Configure path-based deploys so each app rebuilds only when its code (or shared deps) changes.

A typical monorepo on PandaStack

ServiceApp typeRootNotes
webStatic siteapps/webAny framework; built via microVM
apiContainer appapps/apiDATABASE_URL auto-wired
workerContainer appapps/workerRuns worker command
(shared)packages/sharedBuilt as a dependency, not deployed

Handling the database across services

A monorepo often has api and worker sharing one database. Provision it once; both apps in the same project get DATABASE_URL injected. Run migrations from a single owner (usually api as a release step, or a dedicated cronjob) so two services don't race to migrate the same schema.

Build minutes matter

Because monorepo pushes can trigger multiple builds, build minutes add up. Two levers:

  • Change detection so untouched services skip builds entirely.
  • Build caching (Turborepo/Nx) so even rebuilt services restore unchanged packages.

PandaStack plans include build minutes (Free 300, Pro 1000, Premium 2500). Efficient monorepo config keeps you comfortably within them; a naive "rebuild everything" setup burns through them fast.

Common pitfalls

  • Shared package not found: build context too narrow — build from repo root or use workspace filters.
  • Everything redeploys on every push: add path-based triggers / affected-project detection.
  • Lockfile mismatch: commit a single root lockfile and use the matching package manager's workspace install.
  • Migration races: designate one service as the migration owner.

References

  • [Turborepo documentation](https://turbo.build/repo/docs)
  • [Nx documentation](https://nx.dev/getting-started/intro)
  • [pnpm workspaces](https://pnpm.io/workspaces)
  • [Docker: Build context](https://docs.docker.com/build/building/context/)

Monorepo deployments come down to per-service roots, correct shared-package resolution, and building only what changed. PandaStack lets each service point at its own subdirectory of one repo, auto-wires a shared database, and gives you build minutes to work with. Set it up free at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also