From Zero to Production: How I Deployed My App on a VPS Without Losing My Mind

Published: (March 1, 2026 at 08:24 PM EST)
9 min read
Source: Dev.to

Source: Dev.to

The Premise

Every developer has that moment: you’ve built something you’re proud of, it works on your laptop, your friends are waiting, your users are ready… and then someone asks, “Okay, but how do people actually use it?”

That question sent me down a rabbit hole that lasted an entire day—from buying a VPS at midnight, to staring at a blinking cursor on a remote server, to finally watching my app load in a browser on a live domain with proper https://. This is that story.

Why a VPS?

Think of a VPS (Virtual Private Server) like renting a flat instead of a hotel room.

  • Shared hosting (Heroku, Railway, etc.) – the hotel: comfortable, managed, but you share walls with strangers and the rules aren’t yours.
  • VPS – the flat: it’s your space, you control everything, but you’re responsible for your own plumbing.

I chose OVHcloud. Their entry‑level VPS plans are competitive on price—especially compared to AWS or DigitalOcean at equivalent specs. I ordered a VPS with Ubuntu 24.04, which landed in my inbox with SSH credentials within minutes.

TIP: When picking a data‑center region, choose the one closest to where most of your users are. Latency is the silent killer of perceived performance. If your users are in Lagos, don’t host in Oregon.

First Login

Once the email with the VPS IP and root credentials arrives, you get a feeling similar to being handed the keys to an empty apartment. You go in, look around, and realize there’s nothing there yet—no furniture, no décor, just your tools and ambition. So the first thing you do is log in.

ssh root@YOUR_VPS_IP

Update the System

apt update && apt upgrade -y

Locking Down the Server

Why?

The moment your server is provisioned, it’s already being targeted by automated bots scanning for open ports. Your server exists on the public internet—and the public internet is not friendly. Before you install a single dependency or write a single config file, you need to lock down who can even talk to your machine. Think of it as building a fence around your new house.

Install & Configure UFW

UFW (Uncomplicated Firewall) is Ubuntu’s friendly wrapper over the more complex iptables firewall rules. The philosophy: deny everything by default, then only open what you need.

# Install UFW
apt install ufw -y

# Deny ALL incoming connections by default
ufw default deny incoming

# Allow all outgoing (your server needs to reach the internet)
ufw default allow outgoing

# Always allow SSH first — if you forget this and enable the firewall,
# you will lock yourself out permanently. Don't be that person.
ufw allow 22/tcp

# Allow HTTP and HTTPS for the web
ufw allow 80/tcp
ufw allow 443/tcp

# Allow port 3000 for Coolify's dashboard
ufw allow 3000/tcp

# Enable the firewall
ufw enable

Check the rules:

ufw status verbose

Analogy: UFW is the security guard at the entrance of your apartment building. He lets in residents (ports you’ve explicitly allowed) and turns away everyone else. Every port you don’t open is a door that doesn’t exist to the outside world.

Add Active Protection with Fail2ban

UFW is passive; it just blocks doors. What about people actively trying to guess their way in? SSH brute‑force attacks are real. Someone, somewhere, is pointing a bot at every IP on the internet and trying admin/admin, root/password, root/123456, etc., thousands of times per minute. Without protection, your server will just sit there and take it.

Fail2ban watches your logs and temporarily bans IPs that fail too many times—like a bouncer who kicks out repeat offenders.

apt install fail2ban -y

Create a local configuration (never edit the default directly—updates will overwrite it):

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
nano /etc/fail2ban/jail.local

Find the [sshd] section and configure it:

[sshd]
enabled = true
port    = ssh
maxretry = 5
bantime  = 3600
findtime = 600

Enable and start the service:

systemctl enable fail2ban
systemctl start fail2ban

Check who’s been banned:

fail2ban-client status sshd

Analogy: If UFW is the wall around your house, Fail2ban is the doorbell camera that automatically calls the police on anyone who jiggles your handle more than five times.

Your server is now hardened. It has a fence and a bouncer. Nobody who shouldn’t be here is getting in.

Deploying the Application with Coolify

Now comes the fun part—actually putting something on this server. You could go the hard way: manually install Nginx, write config files, manage Docker yourself, wrestle with SSL certs at 2 am. Or you could install Coolify, which does all of that for you through a browser UI.

Coolify is an open‑source, self‑hosted Platform‑as‑a‑Service (PaaS) that abstracts away the low‑level plumbing (Docker, Nginx, SSL, CI/CD) and gives you a clean dashboard to deploy apps, databases, and workers.

(The rest of the guide continues with Coolify installation, app deployment, SSL setup, and post‑deployment tips.)

Coolify – Your Self‑Hosted “Heroku”

Think of Coolify as a Heroku‑ or Railway‑style platform, but you own the server, the data, and you pay the VPS provider directly. It handles:

  • Deployments
  • SSL certificates
  • Environment variables
  • Databases
  • Reverse‑proxying

All through a clean web UI.

1️⃣ Installation (One‑Liner)

wget -q https://get.coollabs.io/coolify/install.sh -O install.sh
sudo bash install.sh

The installer will:

  1. Install Docker (Coolify runs everything in containers)
  2. Set up Coolify’s own containers
  3. Start the service on port 3000

2️⃣ First Run – Setup Wizard

Open a browser and go to:

http://YOUR_VPS_IP:3000

You’ll see the Coolify setup wizard. Create your admin account and you’re ready to go.

Analogy:
Coolify = buying a plot of land and building your own shopping mall (full control).
Heroku = renting a stall in someone else’s mall (convenient but limited).

3️⃣ Add a Database

Every production app needs a persistent data store.

  1. Navigate: Databases → PostgreSQL → New
  2. Choose a version (PostgreSQL 15 or 16)
  3. Give it a name and click Deploy

Coolify provisions the DB inside Docker. Copy the internal connection string:

postgresql://postgres:your_password@YOUR_VPS_IP:5432/postgres

Why PostgreSQL over SQLite?
SQLite is a file‑based DB – great for development, but the file disappears on redeploy. PostgreSQL is a proper, persistent, concurrent server.

4️⃣ Deploy the Backend (API)

4.1 Create a New Application

  • Menu: Applications → New
  • Connect your GitHub repository
  • Select the backend directory
  • Choose Nixpacks as the build pack (auto‑detects Node.js)

4.2 Set Environment Variables

VariableValue
NODE_ENVproduction
DATABASE_URLpostgresql://postgres:your_password@YOUR_VPS_IP:5432/postgres
JWT_SECRETa very long random string
FRONTEND_URLhttps://pos.yourdomain.com
PORT3000

Generate a strong JWT secret (never commit it to Git):

openssl rand -hex 32

4.3 Assign a Domain & Deploy

  • Domain: https://api.yourdomain.com
  • Click Deploy

Coolify will:

  1. Pull the code from GitHub
  2. Build it with Nixpacks
  3. Run it in a Docker container
  4. Expose it via Traefik (built‑in reverse proxy)
  5. Request a free Let’s Encrypt SSL certificate automatically

When /health returns a response, the API is live.

5️⃣ Deploy the Frontend (React/Vite)

5.1 Create Another Application

  • Applications → New (same repo)
  • Select the frontend directory
  • Build pack: Static / Vite

5.2 Environment Variables

VITE_API_URL=https://api.yourdomain.com

5.3 Domain & Rewrite Rule

  • Domain: https://pos.yourdomain.com
  • Static site config – add a rewrite rule so the SPA always serves index.html:
/* → /index.html

Deploy and watch the logs. When you see “Build successful”, you’re good to go.

6️⃣ DNS – Point Your Domains to the VPS

Your services are still reachable only via the IP address. Add A records at your DNS provider (example uses Namecheap):

HostValue (VPS IP)TTL
apiYOUR_VPS_IPAutomatic
posYOUR_VPS_IPAutomatic

DNS propagation can take up to 48 hours, but it’s usually resolved within minutes.

7️⃣ Verify Everything

Open a fresh (non‑incognito) tab:

https://pos.yourdomain.com
  • Login page with a padlock → you’re live.

  • Coolify error / 502 → troubleshoot:

    1. Check backend health: https://api.yourdomain.com/health
    2. View app logs in Coolify (App → Logs)
    3. Verify environment variables (especially DATABASE_URL)

Debugging production means reading logs, not console.log, and each change triggers a redeploy.

8️⃣ Security Extras (UFW & Fail2Ban)

  • UFW → configure allowed ports before enabling the firewall.
  • Fail2Ban → blocks repeated malicious attempts (its name literally means “fail bots until they’re banned”).

Do the firewall first, then install Fail2Ban; otherwise you’ll lock yourself out.

TL;DR Checklist

  1. Install Coolify (one command)
  2. Run the wizard → admin account
  3. Create PostgreSQL DB → copy connection string
  4. Deploy backend → set env vars, domain api.yourdomain.com
  5. Deploy frontend → set VITE_API_URL, domain pos.yourdomain.com, add rewrite rule
  6. Add DNS A records for api & pos → point to VPS IP
  7. Verify SSL (padlock) and health endpoints
  8. Configure UFW & Fail2Ban for security

You now have a fully self‑hosted, production‑ready platform—your own “Heroku” on a VPS. 🚀

✅ Server Setup & Deployment Checklist

StepDescription
1Bought VPS (OVHcloud) — Ubuntu 24.04
2Updated packagesapt update && apt upgrade
3Configured UFW – default deny, opened ports 22, 80, 443, 3000
4Installed Fail2banmaxretry 5, bantime 1h
5Installed Coolify v4 – accessed at :3000
6Provisioned PostgreSQL via Coolify
7Deployed backend (Node.js / Nixpacks) with required environment variables
8Deployed frontend (React / Vite / Static) with SPA rewrite rule
9Configured DNS A records (Namecheap)
10Verified SSL via Let’s Encrypt (automatic in Coolify)
11Tested live endpoints

Result: From a blank server to a live app, start‑to‑finish, in one day. It’s not magic — it’s just knowing the order of operations.

0 views
Back to Blog

Related posts

Read more »

Google Gemini Writing Challenge

What I Built - Where Gemini fit in - Used Gemini’s multimodal capabilities to let users upload screenshots of notes, diagrams, or code snippets. - Gemini gener...