Hey, devs!
Sharing my experience at a startup (let’s call it “VidStack”), I got tasked with deploying a NestJS microservices app on a Ubuntu VPS and AWS. Sounds intimidating? Yeah, it was a bit. But I got through it without setting any servers on fire (mostly). Here’s how I pulled it off—step-by-step, with honest explanations and zero fluff. If you’re a dev figuring out deployment for the first time, this one’s for you. 🍜
🧱 The Setup: What We Were Dealing With
Our app had multiple services (aka microservices). Picture this: 6 microservices yelling at each other over a walkie-talkie (NATS), with an API Gateway playing babysitter. Here’s the crew:
Services:
- API Gateway: The babysitter keeping things in order.
- User Management: Handles signup/login stuff.
- Video Management: Manages video uploads.
- Clips Management: Chops videos into shareable clips.
- Clip Actions: Tracks likes/comments on clips.
- Action Management: Logs all interactions.
All built with NestJS, a slick Node.js framework for backend development.
🧪 Local Dev Setup
Before we go live, we need to test locally. Here’s how I got it running.
🛠 Step 1: Clone the Code
git clone <repo-url>
cd project-spaghetti
What’s this?
We’re grabbing the project code from GitHub and moving into its folder. Think of it as downloading a recipe before cooking. I think I am overexplaining but nvm :).
📦 Step 2: Install Dependencies
npm install
This pulls in all the packages (like express
, dotenv
, etc.) listed in package.json
.
🧪 Step 3: Set Up .env
Files
cp .env.example .env
cp api-gateway/.env.example api-gateway/.env
What are .env
files?
They store sensitive stuff like database passwords. Never commit them to GitHub unless you want hackers sending you thank-you notes.
🐘 Step 4: Start PostgreSQL with Docker
docker-compose up -d postgres
Docker: A tool that runs apps in lightweight containers (like tiny virtual machines).
PostgreSQL: Our database for storing data.
Why Docker? Installing Postgres manually is a hassle. Docker makes it as easy as reheating leftovers.
🗃 Step 5: Run Database Migrations
npx prisma migrate deploy
Prisma: A tool that makes talking to databases feel like chatting with a friend.
Migrations: These create database tables based on your schema (like setting up shelves for your data).
🏃 Step 6: Start the Services
Open a terminal for each service and run:
cd api-gateway && npm run start:dev
Repeat for each microservice. Your laptop will sound like a jet engine, but it works.
🚀 Deploying to a VPS
Now we move from local to real-world. I used a VPS on DigitalOcean (you could use AWS EC2 too).
🌐 Step 1: VPS Initial Setup
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git docker.io docker-compose nginx
What’s happening?
-
apt update/upgrade
: Updates the server’s software. -
curl/git
: Tools for downloading and version control. -
docker/docker-compose
: For running our database in containers. -
nginx
: A web server to handle incoming traffic (we’ll use it soon).
Install Node.js & PM2
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install -g pm2
- Node.js: Runs our NestJS app.
- PM2: A process manager that keeps our services running, even if the server restarts.
🏗 Step 2: Clone Project on VPS
mkdir -p /opt/project-spaghetti
cd /opt/project-spaghetti
git clone <repo-url> .
npm install
npm run install:all
This sets up the app folder and installs all dependencies.
⚙️ Step 3: Configure .env
Files
echo "DATABASE_URL='postgresql://user:pass@localhost:5432/db'" > .env
Set up .env
files again, but with production values (e.g., real database credentials).
🐘 Step 4: Run Database and Migrations
docker-compose up -d
npx prisma migrate deploy
This starts Postgres in Docker and builds the database tables.
🧱 Step 5: Build the Code
npm run build:all
This converts TypeScript code to JavaScript, which Node.js can run.
🧙♂️ Step 6: Keep Services Alive with PM2
pm2 start ecosystem.config.json
pm2 save
- ecosystem.config.json: A file listing all microservices and how to run them.
-
pm2 start
: Launches all services. -
pm2 save
: Ensures they restart if the server reboots.
🌍 Step 7: Set Up Nginx (The Traffic Cop)
sudo nano /etc/nginx/sites-available/project-spaghetti
Paste this:
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://localhost:3000;
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;
}
}
Nginx: A web server that directs internet traffic to your app.
proxy_pass: Sends traffic from port 80 (standard web port) to port 3000 (where our app lives).
Enable the site:
sudo ln -s /etc/nginx/sites-available/project-spaghetti /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
🔒 Step 8: Add Free SSL with Let’s Encrypt
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
This gives your site HTTPS (the browser lock icon). It’s free, and Google loves it.
🤖 Automating with GitHub Actions (CI/CD)
Manually SSH-ing into the server for updates? Nah, let’s automate it.
Set Up SSH for GitHub
ssh-keygen -t ed25519 -C "github-deploy"
Add the public key to your VPS:
echo "<your-public-key>" >> ~/.ssh/authorized_keys
Add the private key and server details to GitHub Secrets:
SSH_PRIVATE_KEY
SERVER_HOST
SERVER_USER
Now, every push to the main
branch will:
- SSH into the server.
- Pull the latest code.
- Rebuild and restart services.
- Let you sip coffee. ☕
🩺 Monitoring & Debugging
PM2 Commands:
pm2 status
pm2 logs
pm2 restart all
Database Backups:
pg_dump -U user db > backup.sql
psql -U user db < backup.sql
Renew SSL:
sudo certbot renew
⚠️ Bonus Tips (Lessons from My Screw-Ups)
- Don’t run migrations before the database is up. You’ll cry.
- Check disk space. My server died once because it was stuffed with logs 😵💫.
- Use a firewall (UFW) to allow only ports 22 (SSH), 80, and 443.
🧠 Final Thoughts
Deploying a NestJS microservices app isn’t trivial, but it’s doable if you break it down:
- Docker runs Postgres.
- Prisma sets up the database.
- PM2 keeps services running.
- Nginx handles traffic.
- GitHub Actions automates updates.
Take it one step at a time, and you’ll be fine. If you’re staring at a blank terminal wondering where to start, I hope this helps. Share it with a dev friend who’s in the same boat! 😅
💭 My Personal Experience & Takeaways
When I first saw the task—"Deploy NestJS microservices to a VPS"—I legit opened 20 tabs, panicked, and drank way too much diet coke.
Challenges I Faced:
- Configuring PM2 to handle multiple services wasn’t easy. Each service needed its own setup, and I kept mixing up ports.
- Forgot to set up a
.env
file once. Debugged for 2 hours. Rookie mistake. - Nginx gave me a 502 Bad Gateway error. Turns out, I forgot to restart it after tweaking the config 🤦♂️.
- Debugging Docker/Postgres issues was brutal without logs. Lesson: always check
docker logs
.
What I Learned:
- Break problems down. One service at a time. One port at a time.
- Deployment is more about configuration than code.
- Tools like Docker, PM2, and Nginx sound scary—but they’re not once you use them.
- CI/CD is a blessing. Automate everything you can.
This task forced me out of my comfort zone, but I came out way more confident with DevOps, deployment, and infrastructure. If you’re reading this and thinking “I can’t do it”—you totally can. Just Google, break it down, and keep pushing.
👋 Final Words
Thanks for sticking around! Hope this helped demystify the world of NestJS deployment for you. If you want more dev logs like this or want to see the GitHub Actions CI/CD setup I used, feel free to reach out or drop a DM!
Happy deploying! ⚙️🚀
— Nainish Rai
Top comments (0)