Setting Up SitecoreAI in a Monorepo Using pnpm + Turborepo

If you’ve followed Sitecore’s official documentation to set up a local SitecoreAI development environment, you’ll know the process is reasonably well documented until you try to do it inside a monorepo. When working with a SitecoreAI monorepo setup, we quickly ran into issues — especially once Docker, Traefik, and a modern frontend stack entered the picture. This post walks through what we ran into, what didn’t work, and the setup that ultimately did.

Who This is For

This post is for full-stack Sitecore developers who are:

  • Setting up a new SitecoreAI project
  • Working on a Windows machine
  • Looking to consolidate their codebase into a monorepo with shared component libraries, tools or other apps

This setup may not be ideal for all projects but if you need to lower operational cost, accelerate developer onboarding, or have a smaller agile team that handles every development aspect, a monorepo structure may be the way to go.

Tech Stack

  • Docker — used for local access to Sitecore CM
  • pnpm + Turbo — package and task management
  • Sitecore CLI — used to facilitate Sitecore Content Serialization
  • Storybook — showcases components built within the monorepo

Understanding the Architecture

The monorepo centralizes code and dependency management for the SitecoreAI site, a Storybook instance, and a shared reusable component library:

sitecoreai-monorepo/
├── apps/
│ └── sitecore/
│ └── cms/ <- Docker CM code and serialized items
│ └── website/ <- Sitecore head app (Content SDK + Next.js)
│ └── storybook/ <- Serves front-end components from the component-library package
├── packages/
│ └── components-library/ <- shared reusable component library
├── package.json <- monorepo-level scripts
├── pnpm-workspace.yaml <- workspace and dependency definitions
├── turbo.json <- task pipeline configuration

It’s also important to understand that local SitecoreAI development in a monorepo involves distinct layers that serve different purposes:

Layer 1 — Local Docker CM (Platform) This is where your Sitecore platform configuration lives — templates, renderings, site configuration, and serialized items. Developers work here to define the data model and structure of the site. Changes are serialized with the Sitecore CLI and deployed to SitecoreAI via CI/CD.

Layer 2 — TypeScript Head App (Rendering Host) Your rendering host runs outside of the Docker context and can be connected to the SitecoreAI Page Builder app to simulate content author editing.

Layer 3 — Component Library/Other Tools (References) Managed within the same repository and referenced by the Sitecore Rendering Host via pnpm and Turborepo

pnpm + Turbo: How They Work Together

We evaluated a few different approaches to managing the monorepo, but pnpm + Turbo struck the right balance between performance and simplicity. In particular, Turbo’s caching and pipeline orchestration made it much easier to keep builds predictable across environments. 

  • pnpm — the package manager. It installs dependencies, manages workspaces, and runs scripts. Think of it as the executorpnpm --filter runs a script in a specific package.
  • Turbo — the task orchestrator. It sits on top of pnpm, determines the order tasks run across packages, caches build outputs, and parallelizes where possible. Think of it as the coordinator.

The pnpm-workspace.yaml file defines which folders are part of the monorepo. Each folder has its own package.json with a name field used to target it in commands (e.g. pnpm --filter @project/sitecore-website). Dependencies reference either catalog: (centrally versioned in pnpm-workspace.yaml) or workspace:* (a local package) to keep versions consistent across the entire repo.

All scripts are defined and run from the monorepo root, delegating to individual packages as needed. For example:

"sitecore:push:master": "sitecore:push:master":"cd apps/sitecore/cms && dotnet sitecore ser push -i testsite-global-master"

One of the biggest practical benefits of this structure is that consuming a shared component in the Sitecore head app is as simple as a standard import using the package name:

import { BodyCopy, Typography } from '@project/components-library'

No publishing, no symlinking manually. pnpm handles workspace resolution automatically.

Why the Rendering Host isn’t Run in Docker

In a standard non-monorepo SitecoreAI setup, the rendering host runs as a Docker container alongside CM. This approach becomes problematic in a monorepo for the following reasons:

  1. pnpm workspace symlinks — pnpm hoists shared dependencies to the root node_modules and uses symlinks to link them across packages. Docker can’t follow these symlinks outside the build context, so the container build fails trying to resolve workspace packages.
  2. Turborepo dependency graph — Turbo needs access to the entire monorepo root to traverse the task pipeline and build dependencies in the correct order. Mounting just the rendering host folder into a container breaks this entirely.
  3. Windows volume mount performance — mounting a large monorepo into a Windows Docker container is notoriously slow, making hot reloading essentially unusable for day-to-day development.

What I initially treated as a workaround, running the rendering host outside Docker via pnpm run sitecore:dev, ended up being the better long-term approach. The CM container reaches it via host.docker.internal:3000, a special DNS name Docker provides for containers to communicate back to the host machine. (This approach may differ on Linux, however, where host.docker.internal may require manual configuration)

This means updating RENDERING_HOST_INTERNAL_URI in your docker-compose.override.yml:

cm:
  environment:
    RENDERING_HOST_INTERNAL_URI: "http://host.docker.internal:3000"

And removing the rendering-nextjs service and its depends_on references from your compose files entirely.

Some Gotchas

Traefik

Getting Traefik working on Windows took more troubleshooting than expected. Here are the specific issues we hit:

1. IIS port conflict — IIS running on the host machine holds ports 80 and 443, preventing Traefik from binding to them. Always run iisreset /stop before bringing up the Docker stack.

2. Trailing backslash in npipe volume mount — this was the most subtle bug. The docker-compose.yml had a trailing backslash on the Docker daemon pipe path:

# Wrong
- source: \\.\pipe\docker_engine\
target: \\.\pipe\docker_engine\
# Correct
- source: \\.\pipe\docker_engine
  target: \\.\pipe\docker_engine

This caused Traefik to fail to connect to the Docker daemon entirely, meaning it couldn’t discover any containers and no routes were registered, despite the container itself being healthy.

3. Traefik versiontraefik:v3.0.4-windowsservercore-ltsc2022 had unresolved bugs with Windows npipe handling and certificate management on Windows 11 24H2. Upgrading to v3.6.4 resolved both issues. As a general rule, avoid early point releases (x.0.x) of major versions for local Sitecore dev on Windows.

Docker

If you are developing on a Windows machine, there are two Docker-specific things you must configure before anything will work:

Windows Containers Mode — Sitecore’s CM, MSSQL, and Solr images are all Windows-based. Docker Desktop defaults to Linux containers, so you need to right-click the Docker tray icon and select “Switch to Windows containers” before running the stack.

Hyper-V Isolation — Windows containers require the container image OS version to match the host kernel. Our dev machines run Windows 11 24H2 (Build 26100) so Sitecore’s ltsc2022 images don’t natively match the host kernel and ltsc2025 variants aren’t available for all Sitecore images yet. Hyper-V isolation solves this by spinning up a lightweight VM per container with its own kernel, bypassing the version mismatch entirely. Set it globally in Docker Desktop under Settings → Docker Engine:

{
  "exec-opts": ["isolation=hyperv"]
}

What I Learned

Setting this up from scratch, we ran into a couple of edge cases that only showed up once everything is wired together. The biggest takeaways:

  • The Traefik npipe trailing backslash issue is a real gotcha that’s essentially impossible to find without knowing what to look for
  • Windows 11 24H2 has meaningful Docker compatibility quirks that require Hyper-V isolation. Don’t assume the latest Windows version will “just work” with Sitecore’s Docker images
  • Running the rendering host outside Docker in a monorepo isn’t a workaround. It’s actually the better developer experience

Another big paradigm shift is that everything runs from the monorepo root and the rendering host is decoupled from Docker. Once that clicks, the rest of the workflow feels natural.

Once that foundation is solid, the rest becomes significantly easier to reason about. And if something feels overly complex, it probably is. Stepping back and simplifying the architecture often leads to a better developer experience than forcing everything into a single pattern.

References

Posted in , , ,

Leave a comment