Back to Blog
Tutorial10 min read2026-08-02

Monorepo Subdirectory Deployments: Shipping Just the Backend

Deploy a single service from a monorepo by setting rootDir to apps/api—PandaStack runs the build in that directory and ignores the rest of the repo.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Most monorepos contain multiple deployable services: a frontend in apps/web, a backend in apps/api, and shared libraries in packages/. When you deploy the backend, you don't want the build system to scan the entire repo, install frontend dependencies, or run unused build steps. PandaStack's rootDir field sets the working directory to a subdirectory before running build commands, so only the backend's package.json is read and only its dependencies are installed.

This post shows how to deploy a single service from a monorepo, how rootDir maps to the build environment, and how to handle shared package dependencies.

The monorepo structure

Assume a typical monorepo layout:

monorepo/
├── apps/
│   ├── web/          # Next.js frontend
│   └── api/          # Express backend
├── packages/
│   └── shared/       # Shared utilities
├── package.json      # Root workspace config
└── pnpm-workspace.yaml

The backend in apps/api/ has its own package.json:

{
  "name": "@monorepo/api",
  "scripts": {
    "dev": "nodemon src/index.js",
    "start": "node src/index.js"
  },
  "dependencies": {
    "express": "^4.18.0",
    "@monorepo/shared": "workspace:*"
  }
}

The workspace dependency @monorepo/shared lives in packages/shared/ and is linked via pnpm workspaces.

Configuring rootDir in pandastack.json

To deploy only the backend, create a pandastack.json at the repo root (not inside apps/api/):

{
  "type": "container",
  "rootDir": "apps/api",
  "language": "nodejs",
  "startCommand": "node src/index.js",
  "healthCheckPath": "/health"
}

The rootDir field tells PandaStack to cd apps/api before running build commands. The builder sees apps/api/package.json, runs pnpm install (or npm install), and ignores the rest of the monorepo.

Critical: the builder still clones the entire repo, so workspace dependencies like @monorepo/shared are available. Pnpm resolves workspace:* to the local packages/shared/ directory at build time.

Deploying via the API

Push the repo to GitHub, then create the project:

curl -X POST https://api.pandastack.io/v1/projects \
  -H "Authorization: Bearer psk_live_your_token" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "container",
    "name": "monorepo-api",
    "repositoryName": "acme/monorepo",
    "branch": "main",
    "autoDeploy": true
  }'

PandaStack reads pandastack.json, detects rootDir: "apps/api", and runs the build in that directory. The frontend in apps/web/ is ignored.

Deploying via the deploy button

If you want a deploy button in the README that deploys only the backend, use the rootDir query param:

[![Deploy API](https://dashboard.pandastack.io/deploy-button.svg)](https://dashboard.pandastack.io/deploy?repo=acme/monorepo&rootDir=apps/api&type=container)

This pre-fills the deploy form with rootDir: apps/api. The pandastack.json approach is cleaner because it keeps the config in version control, but the query param works if you want multiple deploy buttons (one for the frontend, one for the API).

Deploying via the CLI

The CLI doesn't have a direct --root-dir flag, but you can pass it via JSON config:

panda projects create \
  --name monorepo-api \
  --repo acme/monorepo \
  --branch main \
  --type container \
  --start-cmd "node src/index.js"

Then manually set rootDir in the dashboard project settings, or use the API to update it:

curl -X PATCH https://api.pandastack.io/v1/projects/42 \
  -H "Authorization: Bearer psk_live_your_token" \
  -H "Content-Type: application/json" \
  -d '{ "rootDir": "apps/api" }'

Handling workspace dependencies

If your backend depends on packages/shared/, the builder needs access to the shared package. Pnpm, Yarn, and npm workspaces all work because PandaStack clones the entire repo and runs the package manager from the rootDir.

For pnpm, the workspace is defined in pnpm-workspace.yaml at the repo root:

packages:
  - 'apps/*'
  - 'packages/*'

When the builder runs pnpm install in apps/api/, pnpm resolves workspace:* dependencies from the parent directories. No extra config needed.

Yarn/npm workspaces: the same principle applies. The root package.json defines the workspace, and the package manager resolves local packages automatically.

Deploying multiple services from the same repo

To deploy both the frontend and backend, create two separate projects with different rootDir values:

Frontend (pandastack-web.json):

{
  "type": "static",
  "rootDir": "apps/web",
  "buildCommand": "npm run build",
  "outputDir": "dist"
}

Backend (pandastack-api.json):

{
  "type": "container",
  "rootDir": "apps/api",
  "startCommand": "node src/index.js"
}

PandaStack doesn't support multiple config files in one repo, so you have two options:

  1. 1Two projects, one repo: Create two projects in the dashboard, each pointing to the same repo but with different rootDir settings configured via the API or dashboard.
  2. 2Two repos with git subtree: Split the monorepo into separate repos using git subtree split.

Option 1 is simpler and avoids duplicating the repo.

Environment variables per service

Each project has its own environment variables. The frontend might need VITE_API_URL, while the backend needs DATABASE_URL. Configure them separately:

Frontend env:

curl -X POST https://api.pandastack.io/v1/projects/10/env \
  -H "Authorization: Bearer psk_live_your_token" \
  -d '{ "env": [{ "name": "VITE_API_URL", "value": "https://api.example.com" }] }'

Backend env:

curl -X POST https://api.pandastack.io/v1/projects/20/env \
  -H "Authorization: Bearer psk_live_your_token" \
  -d '{ "env": [{ "name": "DATABASE_URL", "value": "postgres://..." }] }'

The projects are independent—changing one doesn't affect the other.

Build performance with rootDir

When rootDir is set, the builder installs only the dependencies needed by that service. For a monorepo with a heavy frontend (Webpack, 2000+ dependencies) and a minimal backend (Express, 50 dependencies), deploying just the backend cuts install time from 3 minutes to under 30 seconds.

The builder caches node_modules between builds, so subsequent deploys are even faster. If you update a shared package, the cache is invalidated and dependencies are reinstalled.

Common issues

Build fails with "package not found": Verify the workspace config (pnpm-workspace.yaml or root package.json workspaces field) includes the rootDir path. If the package manager doesn't recognize the workspace, it can't resolve local dependencies.

Shared package changes not reflected in build: Pnpm caches workspace symlinks. Force a fresh install by clearing the cache in the dashboard (Project Settings → Clear Build Cache) or by pushing a commit that touches the shared package's package.json.

Build runs in the wrong directory: Check that rootDir in pandastack.json matches the actual path. It's relative to the repo root, not the location of pandastack.json itself.

Why this matters

Monorepos centralize code but complicate deployment—most platforms either force you to deploy the entire repo or require complex build filters. Vercel, Netlify, and Render all support rootDir, but their implementations vary in how they handle workspace dependencies and cache invalidation.

PandaStack's approach is straightforward: set rootDir, and the builder changes to that directory before running commands. The entire repo is cloned, so workspace dependencies work out of the box. No custom build scripts or symlink hacks required.

Full example

pnpm-workspace.yaml:

packages:
  - 'apps/*'
  - 'packages/*'

apps/api/package.json:

{
  "name": "@monorepo/api",
  "scripts": {
    "start": "node src/index.js"
  },
  "dependencies": {
    "express": "^4.18.0",
    "@monorepo/shared": "workspace:*"
  }
}

pandastack.json:

{
  "type": "container",
  "rootDir": "apps/api",
  "startCommand": "node src/index.js",
  "healthCheckPath": "/health"
}

Push this to GitHub, create the project via the API or dashboard, and the backend deploys from apps/api/ without touching the frontend.

References

  • [Pnpm Workspaces Documentation](https://pnpm.io/workspaces)
  • [Yarn Workspaces](https://classic.yarnpkg.com/en/docs/workspaces/)
  • [npm Workspaces](https://docs.npmjs.com/cli/v10/using-npm/workspaces)
  • [PandaStack Monorepo Deployment Guide](https://docs.pandastack.io/projects/monorepo)

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also