Docker
Node.js
Monorepo
Yarn Workspaces
Software Development

How to build a docker image from a nodejs project in a monorepo with yarn workspaces

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Building one application from a Yarn-workspace monorepo is mostly about getting the Docker build context right. The image has to see the root lockfile, the workspace manifests that affect dependency resolution, and the application source for the specific package you want to build.

Why Monorepos Need a Different Dockerfile

In a single-package Node.js project, the Dockerfile can usually copy one package.json, install dependencies, copy source, and build. In a monorepo, dependencies are resolved from the repository root, and one workspace may depend on another internal package. If you only copy the target app folder, yarn install will not have enough information to resolve the workspace graph.

Assume this repository layout:

text
1repo/
2  package.json
3  yarn.lock
4  apps/
5    api/
6      package.json
7      src/
8  packages/
9    shared/
10      package.json
11      src/

If apps/api depends on packages/shared, the Docker build must include both workspace manifests before installing dependencies.

A Multi-Stage Dockerfile

The following example works well for a monorepo that builds one service named @acme/api:

dockerfile
1FROM node:20-bookworm-slim AS deps
2WORKDIR /app
3
4RUN corepack enable
5
6COPY package.json yarn.lock ./
7COPY apps/api/package.json apps/api/package.json
8COPY packages/shared/package.json packages/shared/package.json
9
10RUN yarn install --frozen-lockfile
11
12FROM deps AS build
13COPY . .
14RUN yarn workspace @acme/api build
15
16FROM node:20-bookworm-slim AS runtime
17WORKDIR /app
18
19ENV NODE_ENV=production
20
21COPY --from=build /app/package.json ./
22COPY --from=build /app/yarn.lock ./
23COPY --from=build /app/node_modules ./node_modules
24COPY --from=build /app/apps/api ./apps/api
25COPY --from=build /app/packages/shared ./packages/shared
26
27CMD ["node", "apps/api/dist/index.js"]

This Dockerfile has three stages:

  • 'deps installs dependencies using the root lockfile and the workspace manifests'
  • 'build copies the full repository and compiles the target workspace'
  • 'runtime keeps only what is needed to start the service'

That structure improves build caching because dependency installation is invalidated only when the relevant manifest files change.

Build From the Monorepo Root

The build context should be the repository root, not the app directory. From the root of the monorepo:

bash
docker build -t acme-api -f apps/api/Dockerfile .

The trailing . matters because it makes the entire repository available to the Docker daemon. If you run docker build from apps/api, the workspace dependency files outside that folder will be invisible unless you restructure the build.

A .dockerignore file also helps keep the context lean:

text
1node_modules
2dist
3.git
4.yarn/cache
5coverage

Exclude large generated folders, but do not accidentally ignore workspace sources that the target app needs.

Yarn Version Differences

The example above assumes a classic node_modules install flow. If your monorepo uses Yarn Berry with Plug'n'Play or workspaces focus, the runtime stage may look different. The general rule still holds: copy the root dependency metadata first, then the relevant workspace manifests, then the source code.

If you are building only one deployable app from a very large monorepo, specialized tools such as Turborepo pruning or Nx project graph pruning can reduce the Docker context further. That is an optimization step, not a requirement for correctness.

Common Pitfalls

  • Building from the app folder instead of the monorepo root hides sibling workspaces from Docker.
  • Copying source before dependency manifests destroys build-cache efficiency.
  • Forgetting internal workspace packages causes yarn install or the build step to fail.
  • Carrying the entire repository into the runtime image makes the final image larger than necessary.
  • Using Yarn Berry features without copying the related config files, such as .yarnrc.yml, leads to confusing install errors.

Summary

  • Build from the monorepo root so Docker can see the full workspace graph.
  • Copy the root lockfile and relevant workspace manifests before running yarn install.
  • Use a multi-stage Dockerfile to separate dependency install, build, and runtime concerns.
  • Keep the runtime image small by copying only the built app and required dependencies.
  • Adapt the final stage to your Yarn version, but keep the overall build order the same.

Course illustration
Course illustration

All Rights Reserved.