In this post, I would be discussing the approach I followed while deploying one of my hobby projects created using MEVN stack. For this to work you only need to have Docker installed on your system. We'd follow a container-based approach and deploy containers for each individual entity of our project. In case you're interested I'd be using the following project as a reference which I created using MEVN stack.
https://github.com/Apfirebolt/miniurl_mevn
This is a user authentication-based URL shortener app. Logged-in users can submit longer URLs, and the app stores shortened versions of them in the database. Let's break it down into major components for which we need containers
- Back-end written in Express
- MongoDB database
- Nginx as a reverse proxy
For the front-end written in Vue, we won't be using a container. Instead, we'd be serving the build files using Express which would be containerized.
1. Build the front-end
The front-end is written in Vue using Vite. Running the build command would collect the assets generated through Vue inside dist folder which would be served through Express.
npm run build
2. Configure Express to serve Vue build
In this file called server.js inside back-end folder following changes are made to instruct Express server to serve the build files found inside client/dist folder in case environment is "production".
import path from 'path';
import express from 'express';
import dotenv from 'dotenv';
dotenv.config();
import connectDB from './config/db.js';;
const port = process.env.PORT || 5000;
connectDB();
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
if (process.env.NODE_ENV === 'production') {
const __dirname = path.resolve();
app.use(express.static(path.join(__dirname, '/client/dist')));
app.get('*', (req, res) =>
res.sendFile(path.resolve(__dirname, 'client', 'dist', 'index.html'))
);
} else {
app.get('/', (req, res) => {
res.send('API is running....');
});
}
app.listen(port, () => console.log(`Server started on port ${port}`));
3. Add Dockerfile for Node
Inside the root folder of the project a Dockerfile for serving Express application is created.
# Use the official Node.js 22 image as the base image
FROM node:22
# Set the working directory inside the container
WORKDIR /app
# Copy package.json and package-lock.json to the working directory
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code to the working directory
COPY . .
# Expose port 5000
EXPOSE 5000
# Start the application
CMD ["npm", "start"]
Node 22 is used as the base image. App folder is configured to be used as the project root folder. All the dependencies are installed, source files are copied and port 5000 is exposed for communication with other containers.
4. Creating a Docker-compose file
We'd be spawning three services each for back-end, proxy-server and mongoDB database.
version: '3.8'
services:
express:
build:
context: .
dockerfile: Dockerfile
container_name: express_miniurl
ports:
- 5000:5000
depends_on:
- mongo
mongo:
image: mongo
container_name: mongo_miniurl
restart: unless-stopped
volumes:
- ./mongo_data:/data/db
ports:
- '27017:27017'
nginx:
image: nginx
container_name: nginx_miniurl
restart: unless-stopped
ports:
- '80:80'
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- express
volumes:
mongo_data:
external: true
The first service we have is for the Node app which uses the Dockerfile we initialized in the previous step. The second service is for MongoDB, it uses the official Mongo image from the Docker hub. It exposes conventional port 27017 for communication with external containers as well as the host machine in case required. The container has also been explicitly named 'mongo_miniurl' and configured to keep on running unless stopped. It also uses volumes for data persistence as you might be aware that containers don't persist data once they're destroyed.
The third service is an Nginx proxy server that directs traffic from ports 80 and 443 to port 5000, where the Express backend listens.
Notably, the default nginx.conf file is overwritten with a configuration specifically crafted to enable communication with the Express backend. I'd get to this Nginx which resides inside the Nginx folder at the root level in this sample project.
5. Custom Nginx file
In summary, this Nginx configuration file is designed to forward all HTTP requests received on port 80 to the Express backend listening on port 5000. It also sets essential headers to ensure proper communication between the client, Nginx, and the backend server.
events {
worker_connections 1024; # Adjust as needed
}
http {
upstream express {
server express:5000;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://express;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}
Defining the upstream and configuring proxy_pass are key steps in this configuration file. The ability to use the name 'express' stems from its definition as a service name within the Docker Compose file used in the preceding step.
events {
worker_connections 1024; # Adjust as needed
}
The worker_connections directive limits each Nginx worker to 1024 simultaneous client connections. This is suitable for development, but likely needs adjustment for production loads.
6. Configuration Settings adjustments
This project utilizes environment variables defined in an .env file. Create one with the following content:
NODE_ENV=development
PORT=5000
MONGO_URI="mongodb://mongo:27017/mevn_url_shortener"
JWT_SECRET=your-secret-here
The MONGO_URI uses 'mongo' instead of 'localhost' because 'mongo' is the service name defined for our MongoDB database in the Docker Compose file. Docker Compose establishes an internal network, enabling containers to communicate using these service names as hostnames."
That is it folks, you'd now be able to run the project through a simple docker-compose up command.
docker-compose up
The application should be accessible on port 80 if there aren't any other applications using this port on your machine.
Thank you for following along with this tutorial. If you have any questions or insights to share, please leave a comment below. I'd love to hear from you for any suggestions or improvements!
Top comments (0)