Logo

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

Published at
12/31/2024
Categories
webdev
docker
express
vite
Author
jenchen
Categories
4 categories in total
webdev
open
docker
open
express
open
vite
open
Author
7 person written this
jenchen
open
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" ]
Enter fullscreen mode Exit fullscreen mode

Build Docker images and run Docker containers on local machine

Backend

docker build . --target backend --tag backend:latest
Enter fullscreen mode Exit fullscreen mode

Frontend

docker build . --target web --tag web:latest
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Run backend container on the created network

docker run --network=my-net -d -p 8000:8000 backend:latest
Enter fullscreen mode Exit fullscreen mode

Run web container on the created network

docker run --network=my-net -d -p 5173:5173 web:latest
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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" ]
Enter fullscreen mode Exit fullscreen mode

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" ]
Enter fullscreen mode Exit fullscreen mode

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
Image description

Set server url as environment variable to client service

Image description

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

Image description

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]
Enter fullscreen mode Exit fullscreen mode

After the installation is complete, check the pnpm version and make sure it shows the correct target version (here 8.15.6)

pnpm --version
Enter fullscreen mode Exit fullscreen mode

Delete the local pnpm-lock.yaml

rm pnpm-lock.yaml
Enter fullscreen mode Exit fullscreen mode

Execute the pnpm install command to generate a new rm pnpm-lock.yaml file

pnpm i
Enter fullscreen mode Exit fullscreen mode

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

Add files in apps/backend/package.json, and run pnpm deploy again

 "files": [
    "dist"
  ],
Enter fullscreen mode Exit fullscreen mode

4. Error: Cannot find module 'tslib'

After attempting to run the container using the command

docker run -d -p 8000:8000 backend:latest
Enter fullscreen mode Exit fullscreen mode

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"
  },

...
Enter fullscreen mode Exit fullscreen mode

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"
  }

...
Enter fullscreen mode Exit fullscreen mode

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\""
  },
Enter fullscreen mode Exit fullscreen mode

Solution

Use serve to serve the static files

Install serve globally in Dockerfile

RUN pnpm add -g serve
Enter fullscreen mode Exit fullscreen mode

Change this from

...

CMD [ "pnpm", "start" ]
Enter fullscreen mode Exit fullscreen mode

To

...

CMD [ "serve", "-s", "dist", "-l", "5173" ]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Allocate a pseudo-TTY of the target container using the container ID (ae980e452cbe here is the container ID)

docker exec -it ae980e452cbe /bin/sh
Enter fullscreen mode Exit fullscreen mode

List all the environment variables

env
Enter fullscreen mode Exit fullscreen mode

Example output


# env             
NODE_VERSION=23.5.0

...

PWD=/prod/backend
PNPM_HOME=/pnpm
# 
Enter fullscreen mode Exit fullscreen mode
express Article's
30 articles in total
Favicon
Cookies auto clearing after browser refresh issue , CORS related express cookies issue
Favicon
how to setup express api from scratch
Favicon
I Really like Middleware in NodeJs/Express.
Favicon
From Legacy to Lightning: Modernizing an Astro App with Daytona
Favicon
Setting Up Dual Compilation (SSR + CSR) in ViteJS with vite-plugin-builder
Favicon
Starting A Series On TypeScript Join In As We Build Together
Favicon
How to Generate a Secure JWT Secret Using Node.js
Favicon
Mastering Express.js: A Deep Dive
Favicon
Comprendre le Design Pattern MVC avec Node.js, Express et MongoDB
Favicon
How to implement File uploads in Nodejs: A step by step guide
Favicon
Node backend port band bo'lib qolishi
Favicon
Deploying an Existing Express API + Prisma + Supabase Project to Vercel
Favicon
New React Library: API Integration Made Easy with Axiosflow's Automatic Client Generation
Favicon
Build and Deploy a Monorepo WebSocket web application with Turbo, Express, and Vite on Render Using Docker
Favicon
Express app with decorators based routing and dependency injection
Favicon
miniframe-router: Router for Express.JS Applications
Favicon
Create an API for AG-Grid with Express
Favicon
Blogsphere | A blogging website made with MERN stack. includes user management.
Favicon
วิธีทำ Auth API ด้วย Express, JWT, MySQL และ Prisma
Favicon
[Boost]
Favicon
Complete, full-stack setup for any node/express/psql app, equipped with basic ui, routes & more.
Favicon
Which One Should You Choose NEST JS or EXPRESS JS?
Favicon
Hackers Love These Common MERN Stack Mistakes: Are You Exposing Your App? 🔐
Favicon
Build a React login page template
Favicon
A New Way of Setting Up an Express Server for Lazy Developers npm i mbfi
Favicon
[Boost]
Favicon
Express request types
Favicon
Mastering Express.js: A Deep Dive
Favicon
Getting Started with Express.js for Web Development
Favicon
Introduction to TypeScript with Express: Building Your First REST API

Featured ones: