In this article, you will learn how to deploy a MERN (MongoDB, Express, React, Node.js) monorepo using Docker and deploy it on Render for free.
A monorepo contains both the frontend and backend code in a single repository.
Prerequisites
- Signup and install Docker
- Create Dockerfile and Docker image for project
- Signup for Render
Example Project Structure
Your project may likely be structured like the following:
/client
--> src/
--> package.json
/server
--> /public (build files generated by React client)
--> src/
--> .env
--> package.json
Dockerfile
.dockerignore
package.json (root package.json file containing script for deploy)
The client folder would contain a React project created with create-react-app (CRA) or Vite. In that project, you should have a package.json
file containing a build script.
"scripts": {
"start": "react-scripts start",
"build": "BUILD_PATH=../server/public react-scripts build",
...
},
As you can see the build path is set to the /server/public
directory in order to serve the static files using Express.js which also handles any API requests.
In your package.json
file at the root of your project, you may have the following scripts to do project setup and deployment:
"scripts": {
"install-server": "npm install --prefix server",
"install-client": "npm install --prefix client",
"install": "npm run install-server && npm run install-client",
"deploy": "npm run build --prefix client && npm start --prefix server",
...
}
Moving to the server, you may have configuration for connecting to a MongoDB database, signing JWT tokens, sending emails, logging, etc. These services may require sensitive information that you would store using environment variables typically in a .env
file. Here is an example:
NODE_ENV=development
DB_CONNECTION_STRING=<connection-string-here>
JWT_SECRET=<jwt-secret-here>
JWT_EXPIRES_IN=<value-here>
Your project would not be able to run without the values in this file being passed correctly and the best way to do that is to use a secret file in your Docker build. You will see how you can accomplish that using Render.
Introduction to Render
Render is a cloud application platform that makes it easy for developers to build, deploy, and scale their applications. It is designed to provide a seamless and efficient experience for deploying and managing web services, static sites, background workers, and more. Render is a great tool and has key features such as automatic Git deploys, zero-downtime deployments, and more. Also, it has a generous free tier which can be used to deploy a small to medium-sized MERN monorepo project.
Creating and Deploying Web Service with Render
After signing up, on your Render dashboard, select the ‘New’ button then select ‘Web Service’.
After authorizing Render to access your GitHub repositories, your repositories will be listed on Render. From this list, you can easily select the repository containing your project.
When you select your project, Render should automatically detect that your project uses Docker. If it does not, you can manually specify this by selecting Docker from the ‘Language’ dropdown. Render will also automatically pre-fill other settings for you. You can review and modify them as you see fit.
Dealing with Secrets
Render allows you to specify environment variables or to use a secret file that can be accessed during builds and at runtime from your app’s root or from path /etc/secrets/<secret-filename>
.
Since you are utilizing Docker, using a secret file works great. Learn more about using secret files in Docker builds.
Select ‘Add Secret File’, you can go with the standard .env
as the filename and then paste the contents of your .env file.
Dockerfile
Now, you can build your image with a secret mount for that file by adding the following to the top of your Dockerfile:
# syntax = docker/dockerfile:1.2
Then, the following RUN
instruction (considering that you named your secret file .env
)
RUN --mount=type=secret,id=_env,dst=/etc/secrets/.env cat /etc/secrets/.env
Here is an example of a full Dockerfile for a MERN monorepo with the adjustments specified above:
# syntax = docker/dockerfile:1.2
FROM node:lts-alpine
WORKDIR /app
COPY package*.json ./
COPY client/package*.json client/
RUN npm run install-client --omit=dev
COPY server/package*.json server/
RUN npm run install-server --omit=dev
COPY client/ client/
RUN npm run build --prefix client
COPY server/ server/
RUN --mount=type=secret,id=_env,dst=/etc/secrets/.env cat /etc/secrets/.env
USER node
CMD [ "npm", "start", "--prefix", "server" ]
EXPOSE 3001
That’s it! Click ‘Deploy Web Service’ and Render will take care of the rest. You can visit the build logs to resolve any errors you might get. First, ensure you are able to build your project locally without any errors.
Render will provide you with a URL to visit your live site. Also, it has the functionality to use a custom domain.
Resolving MongoDB Network Access Exception
Your new Render site will fail to connect to your MongoDB database if you do not whitelist its IP address to allow network access. To resolve this, visit your MongoDB Atlas cloud dashboard, from the side navigation menu, go to ‘Security’ then ‘Network Access.’ Add the IP addresses of your Render web service, which you can find by clicking your project from the main Render dashboard, which will then navigate to a page giving you all the settings and information on the project. Specifically, select the ‘Connect’ button and you will see your list of IPs.