Skip to content
vibecoder.expert

GUIDE / 2026-07-22

How to Deploy a Lovable App to Your Own VPS

By Published Updated

TL;DR

  • Connect Lovable to GitHub, clone the repo, and you own the code — a standard Vite + React project you can host anywhere.
  • A $6/month VPS with nginx serves a Lovable SPA comfortably; most apps never need more.
  • Frontend-only apps need nginx + `try_files`. Apps with a Node server need PM2 and a reverse proxy — both covered below.
  • Never put secrets in VITE_ variables. They are baked into the public JavaScript bundle at build time.

Lovable’s built-in hosting is fine for a demo. But the moment an app matters — a real domain, real users, custom headers, server logs you can actually read — you want it on infrastructure you control. The good news: every Lovable app is a standard Vite + React project underneath. Once it’s in your GitHub repo, deploying it is the same as deploying any frontend.

This guide takes you from “app lives in Lovable” to “app runs on my own Ubuntu VPS behind nginx with free SSL.” Every command below was tested by author on a fresh Ubuntu 24.04 VPS, July 2026. Budget 30–45 minutes for a first run.

What you need before starting

ItemNotes
A VPS1 vCPU / 1 GB RAM is plenty. DigitalOcean’s basic droplet is $6/mo; Hetzner’s entry cloud server is around €4/mo.
A domainPoint an A record at the VPS IP before you start, so SSL issuance works on the first try.
Your code on GitHubIn Lovable: GitHub → Connect → Create repository. Lovable pushes every edit to this repo from then on.
SSH accessssh root@your-server-ip should work.

One decision matters up front: is your app frontend-only, or does it have a Node server? Most Lovable apps are frontend-only — a React SPA that talks directly to Supabase. Check your repo: if there’s no server/ directory and no Express/Fastify dependency in package.json, you’re in the simple case. Both paths are covered below.

Step 1: Export your code from Lovable

In the Lovable editor, open the GitHub menu and connect your account, then create the repository. This is a real two-way sync, not an export — Lovable commits every change it makes, and you keep full ownership of the code.

Clone it locally and confirm it builds before touching the server:

git clone git@github.com:yourname/your-app.git
cd your-app
npm ci
npm run build

If npm run build produces a dist/ folder locally, it will build on the server. Fix any build errors now — debugging them over SSH is more painful.

Step 2: Prepare the server

SSH in and do the minimum sane hardening. Don’t skip the firewall — a fresh VPS gets scanned by bots within minutes of boot.

ssh root@your-server-ip

# Create a non-root user for deploys
adduser deploy
usermod -aG sudo deploy

# Firewall: SSH + web only
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

apt update && apt upgrade -y
apt install -y nginx git

Step 3: Install Node.js 20

Ubuntu’s default Node is too old. Use NodeSource:

curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
node -v   # should print v20.x

Step 4: Build the app on the server

su - deploy
git clone https://github.com/yourname/your-app.git ~/app
cd ~/app
npm ci
npm run build

sudo mkdir -p /var/www/your-app
sudo cp -r dist/* /var/www/your-app/
sudo chown -R www-data:www-data /var/www/your-app

npm ci (not npm install) installs exactly what’s in your lockfile — the same versions Lovable built against. That difference has bitten me on client rescues more than once: npm install quietly upgraded a dependency and broke a build that “worked yesterday.”

Step 5: Configure nginx

Create /etc/nginx/sites-available/your-app:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    root /var/www/your-app;
    index index.html;

    # SPA routing: every path falls back to index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Long cache for hashed build assets
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    gzip on;
    gzip_types text/css application/javascript application/json image/svg+xml;
    gzip_min_length 1024;
}

Enable it:

sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

The try_files line is the one people miss. Without it, refreshing the browser on /dashboard returns a 404, because nginx looks for a literal dashboard file that doesn’t exist — React Router only works if every route serves index.html.

Step 5b: If your app has a Node server

Some Lovable projects (and most apps migrated from other builders) include an Express or Node API. Static copy won’t work for those — run the server under PM2 and put nginx in front as a reverse proxy:

sudo npm install -g pm2
cd ~/app
pm2 start npm --name your-app -- run start
pm2 save
pm2 startup   # prints a command — run it so the app survives reboots

Then replace the location / block (or add an /api/ block alongside the static one):

    location /api/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

pm2 logs your-app and pm2 monit give you the logs and restart behavior Lovable’s hosting never showed you.

Step 6: Free SSL with certbot

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

Certbot rewrites the nginx config for HTTPS, sets up the HTTP→HTTPS redirect, and installs a systemd timer that renews the certificate automatically. Verify the renewal path once — sudo certbot renew --dry-run — and you never think about it again.

Step 7: Environment variables, done right

This is where vibe-coded deployments go wrong, so read this part twice.

Every VITE_-prefixed variable is public. Vite replaces import.meta.env.VITE_* references with literal strings at build time. They ship inside your JavaScript bundle, and anyone can read them with browser dev tools. That’s fine for values designed to be public — your Supabase URL and anon key — and catastrophic for anything else.

# ~/app/.env.production  — safe: these are public by design
VITE_SUPABASE_URL=https://xyzcompany.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGciOi...

Rules that hold for every stack:

  • Safe in VITE_ vars: Supabase URL, Supabase anon key (protected by Row Level Security, not by secrecy), public analytics IDs.
  • Never in VITE_ vars: Stripe secret keys, OpenAI/Anthropic API keys, the Supabase service_role key, SMTP credentials. If a key can spend money or bypass permissions, it must only exist server-side — in a Supabase Edge Function secret, or in the environment of the Node process from step 5b.
  • Keep .env* in .gitignore, and check it wasn’t already committed: git log --all --oneline -- .env should print nothing. If it prints anything, rotate those keys today — deleting the file doesn’t remove it from history.

Deploying updates

After the first setup, a deploy is four commands. Put them in deploy.sh on the server:

#!/usr/bin/env bash
set -euo pipefail
cd ~/app
git pull
npm ci
npm run build
sudo rsync -a --delete dist/ /var/www/your-app/
echo "Deployed $(git rev-parse --short HEAD)"

You can keep building in Lovable: prompt, let it push to GitHub, then run ./deploy.sh on the server. When you’re ready to automate, a 15-line GitHub Action doing the same steps over SSH turns it into deploy-on-push.

The checklist before you announce it

Deployment working is not the same as production-ready. Before you post the link anywhere: HTTPS forced, RLS enabled on every Supabase table, no secret keys in the bundle (grep -r "sk_live\|sk-ant\|service_role" dist/ should return nothing), and error monitoring wired up. The full 20-point checklist takes five minutes and catches the failures I see most often in rescue work.

Section / FAQ

Questions people ask

Do I lose Supabase when I move off Lovable hosting?
No. Supabase is a separate hosted service — your database, auth, and storage stay exactly where they are. Only the static frontend moves to your VPS. Update your Supabase project's allowed URLs (Auth → URL Configuration) to include your new domain, and everything keeps working.
How big a VPS do I need for a Lovable app?
For a frontend-only app, the smallest tier from any provider is enough — nginx serving static files uses a few megabytes of RAM. 1 vCPU and 1 GB RAM handles thousands of daily visitors. Upgrade only when you add a Node backend or see real traffic.
Can I keep deploying from Lovable after moving to a VPS?
Yes. Lovable's GitHub sync pushes every change to your repo. Add a deploy script (or a GitHub Action that runs `npm run build` and rsyncs `dist/` to the server) and you can keep prompting in Lovable while hosting on your own box.
Why move off Lovable hosting at all?
Control and cost at scale. On your own VPS you choose the domain setup, headers, caching, redirects, logs, and region. You can host several projects on one $6 server, and you are not tied to a subscription to keep the app online.
What if my Lovable app uses edge functions?
Lovable typically creates Supabase Edge Functions, which run on Supabase — not on Lovable's hosting. They keep working after the move. Deploy updates to them with the Supabase CLI (`supabase functions deploy`), separate from your frontend deploys.

Free tool

Run the free security checklist →

20 checks in 5 minutes. Get a PASS/FAIL report before your app meets real users.