Introduction
In this guide, we will build a complete deployment workflow for a MERN application where:
- Frontend (React) is in a separate GitHub repository
- Backend (Node.js/Express) is in a separate GitHub repository
- MongoDB runs in Docker
- Nginx acts as a reverse proxy
- Deployment happens on a VPS
- GitHub Actions automatically deploys on every push to the main branch
By the end, you'll have a production-ready CI/CD pipeline.
Final Architecture
Internet
│
▼
Nginx (VPS)
│
┌──┴───────────┐
│ │
▼ ▼
React App Express API
(Container) (Container)
│
▼
MongoDB
(Container)Project Structure
Frontend Repository
frontend/
│
├── src/
├── public/
├── package.json
├── Dockerfile
└── .github/
└── workflows/
└── deploy.ymlBackend Repository
backend/
│
├── src/
├── package.json
├── Dockerfile
├── docker-compose.yml
└── .github/
└── workflows/
└── deploy.ymlStep 1: Create VPS
Recommended Specs:
2 CPU
4 GB RAM
50 GB SSD
Ubuntu 24.04Providers:
- DigitalOcean
- AWS EC2
- Hetzner
- Contabo
- Hostinger VPS
Step 2: Install Docker
Update server:
sudo apt update
sudo apt upgrade -yInstall Docker:
curl -fsSL https://get.docker.com | shVerify:
docker --versionStep 3: Install Docker Compose
docker compose versionUbuntu 24 usually includes Docker Compose automatically.
Step 4: Backend Dockerfile
Create:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5000
CMD ["npm","start"]Build locally:
docker build -t crud-api .Run:
docker run -p 5000:5000 crud-apiStep 5: Frontend Dockerfile
Create:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
RUN npm install -g serve
EXPOSE 3000
CMD ["serve","-s","build","-l","3000"]Build:
docker build -t crud-frontend .Step 6: Backend Compose File
Create:
services:
api:
image: yourdockerhub/crud-api:latest
container_name: crud-api
ports:
- "5000:5000"
environment:
MONGO_URI: mongodb://mongo:27017/cruddb
depends_on:
- mongo
mongo:
image: mongo:8
container_name: mongo
volumes:
- mongo-data:/data/db
volumes:
mongo-data:Step 7: Frontend Compose File
Create:
services:
frontend:
image: yourdockerhub/crud-frontend:latest
container_name: crud-frontend
ports:
- "3000:3000"Step 8: Create Docker Hub Account
Login:
docker loginTag image:
docker tag crud-api yourdockerhub/crud-api:latestPush:
docker push yourdockerhub/crud-api:latestSame for frontend.
Step 9: Setup Nginx
Install:
sudo apt install nginx -yStep 10: Backend Nginx Config
Create:
sudo nano /etc/nginx/sites-available/apiserver {
server_name api.example.com;
location / {
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Step 11: Frontend Nginx Config
server {
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Enable:
sudo ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginxStep 12: SSL Setup
Install Certbot:
sudo apt install certbot python3-certbot-nginx -yGenerate SSL:
sudo certbot --nginxSelect:
example.com
api.example.comDone.
Step 13: VPS Directory Structure
/opt/apps/
├── backend
│ └── docker-compose.yml
└── frontend
└── docker-compose.ymlStep 14: Create Deployment User
sudo adduser deployAdd docker permission:
sudo usermod -aG docker deployStep 15: Generate SSH Key
On local machine:
ssh-keygen -t ed25519Copy:
cat ~/.ssh/id_ed25519.pubPaste into:
/home/deploy/.ssh/authorized_keysStep 16: GitHub Secrets
Backend Repository:
VPS_HOST
VPS_USER
VPS_SSH_KEY
DOCKER_USERNAME
DOCKER_PASSWORDFrontend Repository:
VPS_HOST
VPS_USER
VPS_SSH_KEY
DOCKER_USERNAME
DOCKER_PASSWORDStep 17: Backend GitHub Action
Create:
.github/workflows/deploy.ymlname: Deploy Backend
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/crud-api:latest .
- name: Push Image
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/crud-api:latest
- name: Deploy VPS
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/apps/backend
docker compose pull
docker compose down
docker compose up -dStep 18: Frontend GitHub Action
name: Deploy Frontend
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/crud-frontend:latest .
- name: Push
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/crud-frontend:latest
- name: Deploy
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/apps/frontend
docker compose pull
docker compose down
docker compose up -dDeployment Flow
Developer pushes code:
git push origin mainGitHub Actions:
1. Checkout code
2. Build Docker image
3. Push image to Docker Hub
4. SSH into VPS
5. Pull latest image
6. Restart containers
7. Deployment completeNo manual server login required.
Useful Commands
View containers:
docker psView logs:
docker logs crud-apiFollow logs:
docker logs -f crud-apiRestart:
docker restart crud-apiStop all:
docker compose downStart all:
docker compose up -dProduction Improvements
For real-world production systems, add:
- Multi-stage Docker builds
- Health checks
- Docker image versioning
- Private Docker registry
- Monitoring (Prometheus + Grafana)
- Log aggregation
- Automated database backups
- Blue-Green deployment strategy
- GitHub Environment approvals
- Kubernetes (future scaling)
Conclusion
This setup provides:
- Separate frontend and backend repositories
- Dockerized applications
- MongoDB in containers
- Nginx reverse proxy
- SSL certificates
- GitHub Actions CI/CD
- Automatic VPS deployments
This is a solid production-grade foundation for most startup and SaaS applications.
Read more
MongoDB: The Complete Guide — Beginner to Advanced
A deep-dive into MongoDB covering core concepts, raw MongoDB shell queries, and Mongoose ODM — with real-world examples at every level.
Redis Complete Guide for Backend Developers
A comprehensive guide to Redis covering in-memory data structures, caching, Pub/Sub, streams, rate limiting, session management, job queues, distributed locking, and production use cases.
Node.js & Express.js: The Complete Guide — Beginner to Advanced
A comprehensive, production-focused deep-dive into Node.js and Express.js — covering core runtime concepts, HTTP fundamentals, REST API design, middleware, authentication, security, testing, performance optimization, and deployment.