Full-Stack Deployment on VM Using Docker
Jan 20, 2025

Full-Stack Deployment on a VM with Docker
A concise guide to deploying a production-ready React + Node.js application using Docker, NGINX reverse proxy, and SSL.
Tech Stack
- React (Vite) frontend
- Node.js / Express backend
- External database (MongoDB Atlas / RDS)
- Ubuntu VM (AWS / DigitalOcean)
- Docker + Docker Compose
- Host NGINX + Certbot (HTTPS)
Project Structure
project-root/
├── frontend/ (Dockerfile, nginx.conf, .env)
├── backend/ (Dockerfile, .env, index.js)
└── docker-compose.yml
Docker Setup
Frontend Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Backend Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD ["node", "index.js"]
Docker Compose
services:
frontend:
build: ./frontend
ports:
- "127.0.0.1:3000:80"
depends_on: [backend]
restart: always
backend:
build: ./backend
ports:
- "127.0.0.1:8080:8080"
env_file: ./backend/.env
restart: always
Containers bind to 127.0.0.1 so only NGINX can access them.
VM Setup
apt update && apt upgrade -y
apt install docker.io nginx certbot python3-certbot-nginx -y
systemctl enable docker nginx
Clone and run:
git clone <repo>
cd project-root
docker compose up --build -d
NGINX Reverse Proxy
server {
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
}
}
Enable and reload:
ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
HTTPS (SSL)
certbot --nginx -d yourdomain.com
Certbot auto-renews certificates via system timer.
Auth & Cookies
Backend:
app.set("trust proxy", 1);
res.cookie("token", token, {
httpOnly: true,
secure: true,
sameSite: "none",
});
Frontend Axios:
const api = axios.create({
baseURL: "/api",
withCredentials: true,
});
Deploy Updates
git pull
docker compose down
docker compose up --build -d
Production Checklist
- Backend not publicly exposed
- HTTPS enabled
- Secure cookies (httpOnly + secure)
- Secrets excluded from Git
- Firewall allows only 22 / 80 / 443
Architecture Overview
Browser → HTTPS → Host NGINX
→ Frontend Container (3000)
→ Backend Container (8080)
This setup provides a secure, scalable deployment pipeline using containers and reverse proxy routing.