Deploying a Scalable MERN Stack Application Using Multi-VM Setup with Vagrant
As applications scale, so does the need for a reliable and scalable deployment environment. In this blog, I'll guide you through deploying a MERN (MongoDB, Express, React, Node.js) stack application using a multi-VM setup with Vagrant. This approach allows us to divide our application into separate virtual machines for the frontend, backend, and database, ensuring better scalability and fault isolation.
Why Use Multi-VM Deployment?
Deploying each part of your application to separate virtual machines allows you to scale individual components independently. For example, if your frontend faces heavy traffic, you can scale that VM without impacting your backend or database performance.
This blog will cover:
Setting up Vagrant for multi-VM deployments
Configuring Nginx as a reverse proxy
Setting up the MERN stack across VMs
Connecting the frontend, backend, and database
Prerequisites
Before you get started, you need:
Basic understanding of the MERN stack (MongoDB, Express, React, Node.js)
Installed Vagrant and VirtualBox on your local machine
Familiarity with Linux commands
1. Project Structure
We’ll divide the MERN stack as follows:
Frontend (React): A dedicated VM serving the React frontend.
Backend (Node.js + Express): A separate VM running Node.js and Express for API calls.
Database (MongoDB): A VM hosting MongoDB.
We’ll use Nginx on both frontend and backend VMs as a reverse proxy for traffic routing.
2. Setting Up the Vagrantfile
The Vagrantfile is a configuration file used by Vagrant to define how VMs are set up and provisioned. Below is a multi-VM configuration for our MERN stack:
Vagrant.configure("2") do |config|
config.vm.define "ngnix_frontend" do |ngnix_frontend|
ngnix_frontend.vm.box = "ubuntu/bionic64"
ngnix_frontend.vm.network "private_network", ip: "192.168.56.101"
ngnix_frontend.vm.hostname = "ngnixFrontend"
ngnix_frontend.vm.provision "shell", inline: <<-SHELL
sudo apt-get update
sudo apt-get install -y nginx
sudo rm /etc/nginx/sites-enabled/default
sudo bash -c 'cat <<EOF > /etc/nginx/sites-available/frontend
server {
listen 80;
server_name frontend.local;
location / {
proxy_pass http://192.168.56.102:3000;
}
}
EOF'
sudo ln -s /etc/nginx/sites-available/frontend /etc/nginx/sites-enabled/frontend
sudo systemctl restart nginx
SHELL
end
# Frontend VM
config.vm.define "frontend" do |frontend|
frontend.vm.box="ubuntu/bionic64"
frontend.vm.network "private_network", ip:"192.168.56.102"
frontend.vm.hostname ="frontend"
frontend.vm.provision "shell", inline: <<-SHELL
sudo apt-get update
sudo apt-get install -y curl
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs
# Set up the frontend (React app)
mkdir -p /home/vagrant/frontend
cd /home/vagrant/frontend
npx create-react-app myapp
cd myapp
npm start
SHELL
end
config.vm.define "nginx_backend_proxy" do |nginx_backend_proxy|
nginx_backend_proxy.vm.box = "ubuntu/bionic64"
nginx_backend_proxy.vm.network "private_network", ip: "192.168.56.103"
nginx_backend_proxy.vm.hostname = "nginx-backend-proxy"
nginx_backend_proxy.vm.provision "shell", inline: <<-SHELL
sudo apt-get update
sudo apt-get install -y nginx
# Configure Nginx to reverse proxy to Backend
sudo rm /etc/nginx/sites-enabled/default
sudo bash -c 'cat <<EOF > /etc/nginx/sites-available/backend
server {
listen 80;
server_name backend.local;
location / {
proxy_pass http://192.168.56.104:5000;
}
}
EOF'
sudo ln -s /etc/nginx/sites-available/backend /etc/nginx/sites-enabled/backend
sudo systemctl restart nginx
SHELL
end
# VM 4: Backend (Node.js)
config.vm.define "backend" do |backend|
backend.vm.box = "ubuntu/bionic64"
backend.vm.network "private_network", ip: "192.168.56.104"
backend.vm.hostname = "backend"
backend.vm.provision "shell", inline: <<-SHELL
sudo apt-get update
sudo apt-get install -y curl
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo apt-get install -y mongodb
# Set up the backend (Node.js)
mkdir -p /home/vagrant/backend
cd /home/vagrant/backend
npm init -y
npm install express mongoose body-parser
# Create a simple backend API in Express
echo "const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
mongoose.connect('mongodb://192.168.56.105/mydb', { useNewUrlParser: true, useUnifiedTopology: true });
app.use(bodyParser.json());
app.get('/api', (req, res) => {
res.send('API is working');
});
const port = 5000;
app.listen(port, () => {
console.log('Server is running on port', port);
});" > app.js
node app.js
SHELL
end
# VM 5: Database (MongoDB)
config.vm.define "database" do |database|
database.vm.box = "centos/7"
database.vm.network "private_network", ip: "192.168.56.105"
database.vm.hostname = "database"
database.vm.provision "shell", inline: <<-SHELL
sudo yum update -y
sudo yum install -y mongodb-org
sudo systemctl start mongod
sudo systemctl enable mongod
# Create a simple database and user
mongo --eval "use mydb"
SHELL
end
end
3. Configuring Nginx as a Reverse Proxy
Nginx on the Frontend VM
Nginx on the frontend VM will act as a reverse proxy, routing requests to the React app and forwarding API requests to the backend VM.
server {
listen 80;
server_name frontend.local;
location / {
proxy_pass http://192.168.56.102:3000; # Proxy to React frontend
}
}
Nginx on the Backend VM
Nginx on the backend VM will forward requests to the Node.js server.
server {
listen 80;
server_name backend.local;
location / {
proxy_pass http://localhost:5000; # Proxy to Node.js backend
}
}
Make sure you place these configurations in /etc/nginx/sites-available/
and create symbolic links in /etc/nginx/sites-enabled/
.
4. Connecting the MERN Stack
The frontend VM serves the React app and forwards API requests to the backend VM.
The backend VM communicates with MongoDB hosted on the database VM.
Ensure MongoDB is bound to the IP
192.168.56.104
in the MongoDB configuration file/etc/mongod.conf
.
MongoDB Connection in Backend
In your Node.js backend, update the MongoDB connection string to point to the database
VM’s private IP:
mongoose.connect('mongodb://192.168.56.104:27017/your-db-name', {
useNewUrlParser: true,
useUnifiedTopology: true
});
5. Bringing Up the VMs
Once you have defined your Vagrantfile and configured Nginx, bring up the VMs by running:
vagrant up
This will spin up all three VMs (frontend, backend, and database), and you can now access your MERN application.
To SSH into individual VMs, use:
vagrant ssh frontend
vagrant ssh backend
vagrant ssh database
6. Testing the Application
Once all VMs are running, open a browser and go to the frontend.local
domain (you may need to update your hosts file to resolve this domain locally). The React frontend will be served, and all API requests will be proxied to the backend.
Make sure to check each VM’s services:
Ensure MongoDB is running on the
database
VM.Ensure the React app is built and running on the
frontend
VM.Ensure the Node.js server is handling API requests on the
backend
VM.
Conclusion
Deploying a MERN application using multiple VMs with Vagrant gives you greater flexibility, especially when scaling and isolating different components. With this setup, each part of your stack can be independently scaled and managed, which is a significant advantage when handling increased traffic or scaling out services.
If you’re looking to scale your application or experiment with distributed architectures, Vagrant and a multi-VM setup provide a simple yet powerful solution for MERN stack deployments.
Subscribe to my newsletter
Read articles from Aryan Gupta directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by