Build Pnpm Workspace Monorepos with Docker Example Guide
Posted November 12, 2023
This guide creates a workflow for setting up a Monorepo with a pnpm-workspace.yaml
file to manage shared dependencies with Docker. You will use Pnpm Workspace as your Monorepo control system. You will then use Docker to Compose a Dockerfile to install dependencies and package the application into a Docker image.
You will learn the following:
- Initialize Monorepo with Pnpm
- Create Package Configurations
- Define Dependencies in Monorepo
- Create Docker Configuration
- Build and run your Monorepo with Docker
Install the latest Node.js on your local computer to proceed with this guide.
Now, dive in and learn how to set up a Pnpm Monorepo with Workspace and dockerizing it.
Related: Complete Guide to Pnpm and Docker with TypeScript & Monorepo
Initialize Monorepo with Pnpm
- Ensure you have Pnpm installed:
npm i -g pnpm
- Create a directory to host the project
mkdir monorepo_demo
- Proceed to your desired directory and initialize the monorepo
cd monorepo_demo && pnpm init
In this project, we will be using Express across the packages we will have. To start, we will install Express on the root project directory as below:
pnpm install express
The next step is to set up the different packages we will have.
Create Monorepo Package Configurations using Pnpm with Workspace
In the project root directory, create a packages
directory. This step will create Monorepo using Pnpm with Workspace.
mkdir packages
In the project directory, we will need to define the package directory in a configuration. To do so, create a pnpm-workspace.yml
file:
packages:
- "./packages/**"
We will have two directories inside the packages folder: main
and shared
. We will have an express application running on the main
directory with a dependency on the shared
directory.
- Create the
shared
directory.
mkdir shared
- Proceed to the
shared
directory and initiliazepnpm
to create apackage.json
:
cd shared && pnpm init
- In the root of the
shared
directory, create anindex.js
file. The file will host a function for outputting aJSON
message as below:
module.exports.testConnection = () => ({
"message":"Connection is good."
});
- Back to the project repository, and create a
main
directory.
mkdir main && cd main
- Initialize pnpm:
pnpm init
Create an app.js
file in the project root directory. In the app.js
file, configure a minimalistic express application as below:
const express = require('express');
const app = express();
const PORT = process.env.PORT ? process.env.PORT : 4000;
app.get("/",(req,res) => res.json({
success:true,
message:"Hello world."
}));
app.listen(PORT, () => console.log("App started and running on PORT "+PORT));
- In the
package.json
file, add a script for running it:
"scripts": {
"start": "node app.js"
}
Define Dependencies Workspace in Monorepo
Now, let’s get the Monorepo Workspace ready. For this, use Pnpm as follows:
- From the terminal of the
main
directory, import the shared package by running this command:
pnpm add @monorepo_demo/shared
- Once the above command has finalized, on your
package.json
file you should have@monorepo_demo/shared
added.
To utilize this shared dependency, on the app.js
:
- import the shared package:
const {testConnection} = require("@monorepo_demo/shared");
- On the message returned on the default
GET
route, change it to be from thetestConnection
function:
app.get("/",(req,res) => res.json({
success:true,
...testConnection()
}));
- Run your application:
pnpm start
Creating Your Pnpm Monorepo for Workspace Docker Configuration
Here comes the fun part: dockerizing your PNPM Monorepo with Docker. Now let’s put it together - pnpm, Monorepo, and Docker.
Integrating these technologies involves containerizing the application for easy deployment.
The Dockerfile will use pnpm to install dependencies and package the application into a Docker image.
Dive in as follows: In the project root directory, create a Dockerfile
:
- Define the image that we will use:
FROM node:20-slim AS node_base
- Define the pnpm environmental variables:
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
- Define the pnpm build configuration:
FROM node_base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run -r build
RUN pnpm deploy --filter=main --prod /prod/main
RUN pnpm deploy --filter=shared --prod /prod/shared
- Define build for main package:
FROM node_base AS main
COPY --from=build /prod/main /prod/main
WORKDIR /prod/main
EXPOSE 4000
CMD [ "pnpm", "start" ]
- Define build for shared package:
FROM node_base AS shared
COPY --from=build /prod/shared /prod/shared
WORKDIR /prod/shared
Build and Run PNPM Monorepo with Docker
Now that the Docker configuration is ready for your PNPM Monorepo, it’s time to run your Workspace Monorepo with Docker.
From the project root directory, build the image for the main
package:
docker build . --target main --tag main:latest
- Similarly, build for
shared
package:
docker build . --target shared --tag shared:latest
- Run the image for
main
:
docker run -p 4000:4000 main
The build will run and expose port 4000
. Test it from your browser, and you should be able to access the below page:
And yes! Your workflow for setting up a Monorepo with a workspace to manage shared dependencies with Docker is working correctly.
Conclusion
Along this guide, you learned how:
- Initialize Monorepo with Pnpm
- Create Package Configurations
- Define Dependencies in Monorepo
- Create Docker Configuration
- Build and run your Monorepo with Docker
Remember to adapt the Dockerfile and other configurations based on your specific project needs. Dive deeper and effectively use the Docker slim strategies to Reduce Docker Image Size.