dev-resources.site
for different kinds of informations.
Build and Deploy a Monorepo WebSocket web application with Turbo, Express, and Vite on Render Using Docker
Introduction
Tip: This post only focuses on the build and deployment phase, and the problems and solutions I encountered. For the project itself, feel free to refer to the full repository on my github here.
Prerequisites
- Install and configure Docker on your local machine and make sure it is running
- Get a Render account
Steps
First, since I'm using pnpm as the package manager for my project, I followed this instruction Working with Docker
to create and build my Dockerfile.
Dockerfile
FROM node:23-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack pnpm --version
RUN pnpm add -g serve
FROM 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=backend --prod /prod/backend
RUN pnpm deploy --filter=web --prod /prod/web
# backend
FROM base AS backend
COPY --from=build /prod/backend /prod/backend
WORKDIR /prod/backend
EXPOSE 8000
CMD [ "pnpm", "start" ]
# web
FROM base AS web
COPY --from=build /prod/web /prod/web
WORKDIR /prod/web
EXPOSE 5173
CMD [ "serve", "-s", "dist", "-l", "5173" ]
Build Docker images and run Docker containers on local machine
Backend
docker build . --target backend --tag backend:latest
Frontend
docker build . --target web --tag web:latest
Because the client of the WebSocket application needs to connect to the server, and the server needs to allow the cross-domain resource sharing of the WebSocket client, it needs to allow services running on the Docker container to communicate with each other.
At the local machine, create a custom, user-defined network my-net
docker network create -d bridge my-net
Run backend container on the created network
docker run --network=my-net -d -p 8000:8000 backend:latest
Run web container on the created network
docker run --network=my-net -d -p 5173:5173 web:latest
However, due to the limitations of Render's free tier, other solutions than setting up user-defined networks are required.
Deploy the web services on Render
Solution 1: using Infrastructure as code
Refer to Render Blueprints (IaC)
Note: I did not choose this approach because credit card info is required at the moment
services:
- type: web
runtime: docker
name: backend
envVars:
- key: SERVER_PORT
value: "8000"
- key: CLIENT_URL
value: "http://frontend.onrender.com"
dockerCommand: |
docker build . --target backend --tag backend:latest && \
docker run -d -p 8000:8000 backend:latest
- type: web
runtime: docker
name: frontend
envVars:
- key: VITE_SERVER_URL
value: "http://backend.onrender.com"
dockerCommand: |
docker build . --target web --tag web:latest && \
docker run -d -p 5173:5173 web:latest
Solution 2: Divide the Dockerfile into 2 parts, respectively for backend and frontend
Since Render will automatically run the docker build based on the Dockerfile of the target repo, in order to create 2 web services and allow them to communicate with each other, create 2 Dockerfile in each project based on above Dockerfile:
apps/backend/Dockerfile
FROM node:23-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack pnpm --version
FROM 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=backend --prod /prod/backend
FROM base AS backend
COPY --from=build /prod/backend /prod/backend
WORKDIR /prod/backend
EXPOSE 8000
CMD [ "pnpm", "start" ]
apps/web/Dockerfile
FROM node:23-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack pnpm --version
RUN pnpm add -g serve
FROM 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=web --prod /prod/web
FROM base AS web
COPY --from=build /prod/web /prod/web
WORKDIR /prod/web
EXPOSE 5173
CMD [ "serve", "-s", "dist", "-l", "5173" ]
Then follow instructions to deploy these two web services
Note:
Before deployment, set the environment variables here and set the client url and server url of the back-end service and front-end service respectively, as shown below
Set client url as environment variable to backend service
Set server url as environment variable to client service
Problems and Solutions
1. Error: tsconfig.json:4:5 - error TS6310: Referenced project '/socket-react-fullstack-monorepo/apps/web/tsconfig.app.json' may not disable emit
Root cause
TBC
Solution
Add "files": [],
to apps/web/tsconfig.json
2. Error: ERR_PNPM_LOCKFILE_BREAKING_CHANGE  Lockfile /usr/src/app/pnpm-lock.yaml not compatible with current pnpm
During building docker file RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
Root cause
Because RUN corepack
enable in the Dockerfile downloads pnpm version 8.15.6, but the pnpm-lock.yaml file generated by executing pnpm i
is generated by the pnpm package installed locally (through npm install -g pnpm
), which is version 9.15.1. So version mismatch will cause errors
Solution
Change (downgrade) the locally installed pnpm version to match the target version downloaded by corepack in the Dockerfile, which is 8.15.6. and generate a new pnpm-lock.yaml with the target version of pnpm
npm install -g [email protected]
After the installation is complete, check the pnpm version and make sure it shows the correct target version (here 8.15.6)
pnpm --version
Delete the local pnpm-lock.yaml
rm pnpm-lock.yaml
Execute the pnpm install command to generate a new rm pnpm-lock.yaml file
pnpm i
3. Error: After executing the commands pnpm run -r build
and pnpm deploy --filter=backend --prod /prod/backend
, there is no dist/ folder in /prod/backend
Root cause
Missing files
field in package.json
Solution
- https://github.com/pnpm/pnpm/issues/5020
- https://pnpm.io/cli/deploy
- https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files
Add files in apps/backend/package.json, and run pnpm deploy
again
"files": [
"dist"
],
4. Error: Cannot find module 'tslib'
After attempting to run the container using the command
docker run -d -p 8000:8000 backend:latest
Root cause
The package tslib in apps/backend/package.json is in devDependencies
, however, according to tslib it should be in dependencies
...
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.14.12",
"nodemon": "^3.1.4",
"tslib": "^2.6.3",
"typescript": "^5.3.3"
},
...
Solution
Move tslib
to dependencies
and run pnpm i
to generate pnpm-lock.yaml based on the new dependencies in apps/backend/package.json
...
"dependencies": {
"tslib": "^2.6.3",
"cors": "^2.8.5",
"express": "^4.19.2",
"socket.io": "^4.7.5",
"ts-node": "^10.9.2",
"zod": "^3.23.8"
}
...
5. Error: ERR_PNPM_NO_SCRIPT_OR_SERVER  Missing script start or file server.js
When trying to launch the Vite application using pnpm start
in a Docker container
Root cause
There is no start
in the script in apps/web/package.json
"scripts": {
"dev": "vite --clearScreen false",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint \"src/**/*.ts\""
},
Solution
Use serve to serve the static files
Install serve globally in Dockerfile
RUN pnpm add -g serve
Change this from
...
CMD [ "pnpm", "start" ]
To
...
CMD [ "serve", "-s", "dist", "-l", "5173" ]
Build and run the Docker container again in detached mode (-d
), using the web:latest
image to map port 5173 on the host to port 5173 in the container
docker run -d -p 5173:5173 web:latest
Resources
How to debug a running Docker container: using docker container exec
For example, check whether certain environment variables exist in the executing docker container.
List all running docker containers and view the container ID of the target container
docker ps
Allocate a pseudo-TTY of the target container using the container ID (ae980e452cbe here is the container ID)
docker exec -it ae980e452cbe /bin/sh
List all the environment variables
env
Example output
# env
NODE_VERSION=23.5.0
...
PWD=/prod/backend
PNPM_HOME=/pnpm
#
Featured ones: