Back to blog
#SvelteKit #DevOps #VPS #Deployment

Deploying SvelteKit on a VPS: The Setup I Now Use for Every Project

Vercel is great until you need persistent storage, custom cron jobs, or a fixed monthly cost. This is the Nginx + PM2 + GitHub Actions setup I landed on for Dharentrans and now use as my default deployment stack.

Muhamad Taufik Muhamad Taufik
· Oct 3, 2025 · 9 min read
Server rack in a data center
React:

After shipping several projects on Vercel, there comes a time when you need something more — persistent storage, cron jobs, and a fixed monthly cost. That’s what ultimately pushed me toward a VPS for projects like Dharentrans.com.

This is the setup I now use as a default template for every SvelteKit project that needs SSR.

The Stack

  • SvelteKit with adapter-node
  • Nginx as a reverse proxy
  • PM2 for process management
  • GitHub Actions for automated CI/CD
  • VPS: Ubuntu 22.04 (Hetzner CPX11, €4.35/month)

1. SvelteKit Setup with adapter-node

First, make sure you’re using adapter-node, not adapter-auto:

npm i -D @sveltejs/adapter-node

Update svelte.config.js:

import adapter from "@sveltejs/adapter-node";

export default {
  kit: {
    adapter: adapter({
      out: "build",
    }),
  },
};
npm run build
# Output: /build folder
node build
# Server running on port 3000

2. VPS Setup (Ubuntu 22.04)

Log into the VPS, install Node.js and PM2:

# Install Node.js via nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 20
node -v  # v20.x.x

# Install PM2 globally
npm install -g pm2

3. Nginx Configuration

Create a config for your domain:

sudo nano /etc/nginx/sites-available/dharentrans.com
server {
    listen 80;
    server_name dharentrans.com www.dharentrans.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_set_header X-Real-IP $remote_addr;
        proxy_cache_bypass $http_upgrade;
    }
}
sudo ln -s /etc/nginx/sites-available/dharentrans.com /etc/nginx/sites-enabled/
sudo nginx -t   # Test config
sudo systemctl reload nginx

Enable HTTPS with Certbot:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d dharentrans.com -d www.dharentrans.com

4. PM2 Ecosystem File

Create ecosystem.config.cjs in the project root:

module.exports = {
  apps: [
    {
      name: "dharentrans",
      script: "build/index.js",
      env: {
        NODE_ENV: "production",
        PORT: 3000,
        ORIGIN: "https://dharentrans.com",
      },
    },
  ],
};
pm2 start ecosystem.config.cjs
pm2 save         # Save process list
pm2 startup      # Auto-start on reboot

5. CI/CD with GitHub Actions

Create .github/workflows/deploy.yml:

name: Deploy to VPS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install & Build
        run: |
          npm ci
          npm run build

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            cd /var/www/dharentrans
            git pull origin main
            npm ci --omit=dev
            npm run build
            pm2 restart dharentrans

Add secrets in your GitHub repository settings:

  • VPS_HOST — your VPS IP
  • VPS_USER — username (usually root or ubuntu)
  • VPS_SSH_KEY — private SSH key

Extra Tips

Swap space — Hetzner doesn’t enable swap by default. If you frequently run out of memory during builds:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Environment variables — don’t hardcode in your codebase, store in .env on the server:

nano /var/www/dharentrans/.env
DATABASE_URL=mongodb://localhost:27017/dharentrans
MIDTRANS_SERVER_KEY=your-key

Conclusion

I’ve been running this setup on Dharentrans since October 2025 and it’s been rock solid. Total cost: €4.35/month for a Hetzner VPS — far cheaper than Vercel Pro once traffic starts climbing.

For small-to-medium projects, this is more than enough.

Comments (0)

Join the conversation

You need to sign in to leave a comment or reaction.

Loading comments...