Client Area

Full-Stack Web App Setup on a Fresh VPS (With Docker, Node.js, Prisma, Authentication, Frontend, and HTTPS)

8 min readPublished 4 Mar 2026Updated 14 Apr 2026970 views

In this article

  • 1Article Summary
  • 2Key Concepts & Terms Explained - Glossary for Developers & DevOps
  • 31. Start with a Fresh VPS
  • 42. Install Docker and Docker Compose
  • 53. Project Structure Example

Article Summary

This comprehensive guide walks you through setting up a modern full-stack web application on a freshly provisioned VPS. You'll learn how to:

  • Install system updates and development tools

  • Set up Docker and Docker Compose

  • Scaffold and run a Node.js backend using Express

  • Use Prisma as your ORM

  • Secure APIs with JWT authentication

  • Create a frontend with React or Next.js

  • Configure Nginx as a reverse proxy

  • Enable HTTPS using Certbot and Let's Encrypt


Key Concepts & Terms Explained - Glossary for Developers & DevOps

Term What It Means Why It Matters Here
VPS (Virtual Private Server) A private slice of a physical server with its own OS and full root access. Enables full-stack deployments with Docker, Node.js, and more.
Docker Container system that packages apps with all their dependencies. Ensures consistent environments between development and production.
Docker Compose Tool to define and run multi-container apps via docker-compose.yml. Spins up backend, frontend, and Nginx together--simplifies orchestration.
Node.js A JavaScript runtime built on V8 for server-side development. Runs the Express backend logic and handles API endpoints.
Express.js Lightweight web framework for Node.js. Powers REST APIs like /login, /register, etc.
NPM / package.json Node Package Manager and its manifest file. Manages dependencies like bcrypt, jwt, prisma, and defines scripts.
ORM (Object Relational Mapper) Maps DB tables to code objects, avoiding raw SQL. Simplifies data operations using intuitive syntax.
Prisma Modern ORM for Node.js with auto-generated queries. Handles DB connections, schema, and migration logic efficiently.
Migration Version-controlled changes to the DB schema. Keeps database structure in sync across dev/staging/production.
SQLite / PostgreSQL / MySQL Relational DBs--SQLite for dev, PostgreSQL/MySQL for production. Choose based on scale, performance, and production needs.
JWT (JSON Web Token) A compact, signed token for user authentication. Enables secure and stateless auth between frontend and backend.
Bcrypt Secure hashing algorithm for passwords. Stores hashed passwords for robust authentication security.
React / Next.js React = frontend UI library; Next.js = React + server-side rendering. Renders dynamic UIs and fetches API data.
Axios HTTP client for making API requests. Used in React to connect to Express backend securely.
Nginx Web server and reverse proxy for routing and load balancing. Forwards /api/* to backend and serves static frontend.
Reverse Proxy Public-facing server that redirects traffic internally. Allows backend/frontend separation behind port 80/443.
Certbot Let's Encrypt automation tool for HTTPS setup. Quickly secures your site with free SSL.
Let's Encrypt Free SSL certificate authority. Powers HTTPS without the cost of paid certs.
.env File Configuration file storing environment variables. Keeps secrets like DB credentials and JWT keys secure.
docker-compose.yml Blueprint to define Docker services and networks. Used to start the entire stack in one command.
Volume (Docker) Persistent data store outside of containers. Ensures data like logs, DB files, etc. are not lost on container restart.
PM2 Node.js process manager for production. Keeps backend alive, auto-restarts on crash, and logs events.
GitHub Actions (CI/CD) Automates builds, tests, and deployments from your repo. Ensures consistent releases and automated testing.
CORS Browser rule for cross-domain requests. Enables frontend on port 3000 to access backend on 5000.
Helmet Express middleware for setting secure HTTP headers. Prevents common web attacks (XSS, clickjacking).
Rate Limiter Middleware that limits requests per IP. Prevents brute-force login attempts and abuse.
CI/CD Continuous Integration / Deployment pipeline. Automates building and deploying full-stack apps.
JWT Refresh Flow (Optional) Issues long-term refresh tokens and short-term access tokens. Prevents session expiry without user re-login.
Twelve-Factor App 12 rules for modern app development (env vars, logs, stateless). Your project structure aligns with these SaaS best practices.

1. Start with a Fresh VPS

sudo apt update && sudo apt upgrade -y
sudo apt install curl wget git unzip nano -y

2. Install Docker and Docker Compose

curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
sudo usermod -aG docker $USER
newgrp docker

# Install Docker Compose v2
sudo apt install docker-compose-plugin

3. Project Structure Example

project/
 backend/
 src/
 prisma/
 .env
 package.json
 Dockerfile
 frontend/
 public/
 src/
 .env
 package.json
 Dockerfile
 docker-compose.yml
 nginx/
 default.conf

4. Backend Setup (Node.js + Express)

Initialize Backend:

mkdir -p backend/src backend/prisma
cd backend
npm init -y
npm install express dotenv bcryptjs jsonwebtoken @prisma/client
npm install -D nodemon prisma

Update package.json

"scripts": {
 "dev": "nodemon src/index.js",
 "start": "node src/index.js"
}

Sample src/index.js

require('dotenv').config();
const express = require('express');
const app = express();
const port = process.env.PORT || 5000;

app.use(express.json());

app.get('/', (req, res) => res.send('API Running'));

app.listen(port, () => {
 console.log(` Backend server is running at http://localhost:${port}`);
});

Create .env

PORT=5000
DATABASE_URL="file:./dev.db"
JWT_SECRET=your_secret_key

5. Add Prisma ORM

Initialize Prisma:

npx prisma init

Example prisma/schema.prisma

datasource db {
 provider = "sqlite"
 url = env("DATABASE_URL")
}

generator client {
 provider = "prisma-client-js"
}

model User {
 id Int @id @default(autoincrement())
 email String @unique
 password String
}

Apply Migration and Generate Client:

npx prisma migrate dev --name init

Use in Code:

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

6. JWT Authentication

Register & Login Endpoints (Basic Example)

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

// Registration
app.post('/register', async (req, res) => {
 const { email, password } = req.body;
 const hashed = await bcrypt.hash(password, 10);
 const user = await prisma.user.create({ data: { email, password: hashed } });
 res.json(user);
});

// Login
app.post('/login', async (req, res) => {
 const { email, password } = req.body;
 const user = await prisma.user.findUnique({ where: { email } });
 if (!user || !(await bcrypt.compare(password, user.password))) {
 return res.status(401).json({ error: 'Invalid credentials' });
 }
 const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
 res.json({ token });
});

7. Frontend Setup (React or Next.js)

Example: Create React App

npx create-react-app frontend
cd frontend
npm install axios react-router-dom

.env

REACT_APP_API_URL=http://localhost:5000

Fetching API from React

import axios from 'axios';

const login = async () => {
 const response = await axios.post(`${process.env.REACT_APP_API_URL}/login`, {
 email: '[email protected]',
 password: 'secret'
 });
 localStorage.setItem('token', response.data.token);
};

8. Dockerize Backend and Frontend

Backend Dockerfile

FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "run", "dev"]

Frontend Dockerfile

FROM node:18
WORKDIR /app
COPY . .
RUN npm install && npm run build
EXPOSE 3000
CMD ["npx", "serve", "-s", "build"]

9. Docker Compose

version: '3.8'
services:
 backend:
 build: ./backend
 ports:
 - "5000:5000"
 env_file:
 - ./backend/.env
 volumes:
 - ./backend:/app

 frontend:
 build: ./frontend
 ports:
 - "3000:3000"
 env_file:
 - ./frontend/.env
 volumes:
 - ./frontend:/app

 nginx:
 image: nginx:latest
 volumes:
 - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
 ports:
 - "80:80"
 depends_on:
 - backend
 - frontend

10. Nginx Reverse Proxy Setup

Example nginx/default.conf

server {
 listen 80;

 location /api/ {
 proxy_pass http://backend:5000/;
 proxy_set_header Host $host;
 }

 location / {
 proxy_pass http://frontend:3000/;
 proxy_set_header Host $host;
 }
}

11. HTTPS via Certbot (Let's Encrypt)

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Auto-renew:

sudo crontab -e
# Add:
0 3 * * * certbot renew --quiet

12. Next Steps

Once your full-stack application is functional, you can take it to the next level with these enhancements and production-level improvements:

Use PostgreSQL or MySQL with Prisma

Instead of the default SQLite or in-memory databases, integrate a robust relational database like PostgreSQL or MySQL.

  • Install the client library:

    npm install @prisma/client
    npm install prisma --save-dev
    npm install pg # For PostgreSQL
    # or
    npm install mysql2 # For MySQL
  • Configure Prisma schema (prisma/schema.prisma):

    datasource db {
     provider = "postgresql"
     url = env("DATABASE_URL")
    }
  • Set the database URL in .env:

    DATABASE_URL="postgresql://username:password@localhost:5432/mydb"
  • Migrate the database:

    npx prisma migrate dev --name init

Add Tests (Jest, Supertest)

Improve code reliability with automated tests:

  • Install testing libraries:

    npm install --save-dev jest supertest @types/jest ts-jest
  • Basic test example (tests/app.test.js):

    const request = require('supertest');
    const app = require('../src/app');
    
    describe('GET /', () => {
     it('should return 200 OK', async () => {
     const res = await request(app).get('/');
     expect(res.statusCode).toEqual(200);
     });
    });
  • Add to package.json:

    "scripts": {
     "test": "jest"
    }

Set Up CI/CD (GitHub Actions)

Automate your testing and deployment process.

  • Sample GitHub Actions workflow .github/workflows/ci.yml:

    name: CI
    on:
     push:
     branches: [main]
    
    jobs:
     build:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v3
     - name: Set up Node.js
     uses: actions/setup-node@v3
     with:
     node-version: '18'
     - run: npm install
     - run: npm run test

Deploy on VPS

  • Clone your repo on the server

  • Use PM2 or Docker to manage Node.js processes

  • Set up Nginx as a reverse proxy

Example PM2 setup:

npm install pm2 -g
pm run build
pm start
pm2 start index.js --name myapp
pm2 save && pm2 startup

Add CORS, Helmet, Rate Limiters

Secure your backend with best practices.

  • CORS:

    npm install cors
    const cors = require('cors');
    app.use(cors());
  • Helmet:

    npm install helmet
    const helmet = require('helmet');
    app.use(helmet());
  • Rate Limiter:

    npm install express-rate-limit
    const rateLimit = require('express-rate-limit');
    const limiter = rateLimit({
     windowMs: 15 * 60 * 1000,
     max: 100
    });
    app.use(limiter);

With these enhancements, your project will be more robust, secure, testable, and production-ready.

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket