What Is a Monorepo?
A monorepo stores multiple projects — frontend, backend, shared libraries, infrastructure config — in a single Git repository. Instead of a separate repo per service (polyrepo), everything lives together. Google, Meta, and Airbnb famously operate at massive scale with monorepos.
The primary challenge for deployment is selectivity: when you change only the frontend, you do not want to rebuild and redeploy the backend. Getting this right is what separates a well-architected monorepo from a slow, expensive mess.
Monorepo Directory Structure
my-monorepo/
├── apps/
│ ├── web/ ← React frontend
│ ├── api/ ← Node.js backend
│ └── worker/ ← Background job processor
├── packages/
│ ├── ui/ ← Shared React components
│ ├── utils/ ← Shared utilities
│ └── types/ ← Shared TypeScript types
├── panda.web.yaml ← PandaStack config for frontend
├── panda.api.yaml ← PandaStack config for backend
└── package.json ← Workspace rootUsing a package manager with workspace support (npm workspaces, pnpm workspaces, or Yarn workspaces) lets you install all dependencies from the root and share code between packages without publishing to npm.
# package.json (root)
{
"name": "my-monorepo",
"workspaces": [
"apps/*",
"packages/*"
]
}Path-Filtered CI: Deploy Only What Changed
The key to efficient monorepo CI/CD is path filtering — running a job only when files in a specific directory change:
# .github/workflows/deploy-web.yml
name: Deploy Web Frontend
on:
push:
branches: [main]
paths:
- 'apps/web/**'
- 'packages/ui/**' # also rebuild web if shared UI changes
- 'packages/types/**'
jobs:
deploy-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build web app
run: npm run build --workspace=apps/web
- name: Deploy to PandaStack
run: |
npm install -g @pandastack/cli
panda login --token ${{ secrets.PANDA_TOKEN }}
panda deploy --config panda.web.yaml --branch main# .github/workflows/deploy-api.yml
name: Deploy API Backend
on:
push:
branches: [main]
paths:
- 'apps/api/**'
- 'packages/utils/**'
- 'packages/types/**'
jobs:
deploy-api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test --workspace=apps/api
- name: Deploy to PandaStack
run: |
npm install -g @pandastack/cli
panda login --token ${{ secrets.PANDA_TOKEN }}
panda deploy --config panda.api.yaml --branch mainPandaStack Configuration per Service
Each service gets its own PandaStack configuration file:
# panda.api.yaml
name: my-app-api
type: container
branch: main
build:
dockerfile: apps/api/Dockerfile
context: .
env:
NODE_ENV: production
PORT: "3000"
resources:
cpu: 500m
memory: 512Mi# panda.web.yaml
name: my-app-web
type: static
branch: main
build:
command: npm run build --workspace=apps/web
output: apps/web/distAffected-Package Detection
For more sophisticated change detection — where you want to rebuild services that depend on changed packages — use a tool like turbo or write a custom script:
#!/bin/bash
# scripts/affected.sh
# Determine which apps changed since last deploy
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD)
deploy_web=false
deploy_api=false
echo "$CHANGED_FILES" | grep -qE '^(apps/web|packages/ui)/' && deploy_web=true
echo "$CHANGED_FILES" | grep -qE '^(apps/api|packages/utils)/' && deploy_api=true
echo "deploy_web=$deploy_web" >> $GITHUB_OUTPUT
echo "deploy_api=$deploy_api" >> $GITHUB_OUTPUT detect-changes:
runs-on: ubuntu-latest
outputs:
deploy_web: ${{ steps.affected.outputs.deploy_web }}
deploy_api: ${{ steps.affected.outputs.deploy_api }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- id: affected
run: bash scripts/affected.shShared Libraries and Versioning
When a shared package changes, all services that depend on it should be rebuilt. Document dependencies explicitly:
# .github/workflows/rebuild-all.yml
name: Rebuild All Services
on:
push:
branches: [main]
paths:
- 'packages/types/**' # Core types — everything rebuilds
jobs:
deploy-all:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build # Build all workspaces
- run: |
npm install -g @pandastack/cli
panda login --token ${{ secrets.PANDA_TOKEN }}
panda deploy --config panda.web.yaml --branch main
panda deploy --config panda.api.yaml --branch mainWith path filtering in place, a monorepo on PandaStack gives you the collaboration benefits of a unified codebase with the deployment efficiency of independent services — each deploying only when its code actually changes.