Skip to content

Deploy to VPS

Deploy Portfolio CMS to a Virtual Private Server for full control over your infrastructure.

Prerequisites

  • VPS with Ubuntu 22.04+ (DigitalOcean, Linode, Vultr, etc.)
  • Domain name pointed to your server
  • SSH access to your server
  • Basic Linux command line knowledge
ResourceMinimumRecommended
CPU1 vCPU2 vCPU
RAM2 GB4 GB
Storage20 GB SSD40 GB SSD
OSUbuntu 22.04Ubuntu 22.04

Server Setup

Step 1: Initial Server Configuration

bash
# Connect to your server
ssh root@your_server_ip

# Update system
apt update && apt upgrade -y

# Create deploy user
adduser deploy
usermod -aG sudo deploy

# Setup SSH for deploy user
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

# Switch to deploy user
su - deploy

Step 2: Install Node.js

bash
# Install Node.js 20 via NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Verify installation
node --version
npm --version

# Install pnpm
sudo npm install -g pnpm

Step 3: Install PostgreSQL

bash
# Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib

# Create database and user
sudo -u postgres psql

# In PostgreSQL:
CREATE USER portfolio WITH PASSWORD 'your_secure_password';
CREATE DATABASE portfoliocms OWNER portfolio;
GRANT ALL PRIVILEGES ON DATABASE portfoliocms TO portfolio;
\q

Step 4: Install Nginx

bash
# Install Nginx
sudo apt install -y nginx

# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx

# Check status
sudo systemctl status nginx

Step 5: Install PM2

bash
# Install PM2 globally
sudo npm install -g pm2

# Setup PM2 startup
pm2 startup systemd
# Follow the instructions it outputs

Application Setup

Step 1: Clone Repository

bash
# Create app directory
sudo mkdir -p /var/www/portfolio
sudo chown deploy:deploy /var/www/portfolio

# Clone your repository
cd /var/www/portfolio
git clone https://github.com/yourusername/portfoliocms.git .

Step 2: Install Dependencies

bash
# Install dependencies
pnpm install

Step 3: Configure Environment

bash
# Create .env file
nano .env

Add your environment variables:

env
DATABASE_URL=postgresql://portfolio:your_secure_password@localhost:5432/portfoliocms
PAYLOAD_SECRET=your-super-secret-key-at-least-32-characters
NEXT_PUBLIC_SITE_URL=https://yourdomain.com

S3_BUCKET=your-bucket-name
S3_ACCOUNT_ID=your-account-id
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET=your-secret-key

RESEND_API_KEY=re_...
RESEND_FROM_EMAIL=...
RESEND_FROM_NAME=...

NODE_ENV=production
PORT=3000

Step 4: Build Application

bash
# Generate types
pnpm generate:types

# Run migrations
pnpm payload migrate

# Build
pnpm build

Step 5: Start with PM2

bash
# Start the application
pm2 start pnpm --name "portfolio" -- start

# Save PM2 configuration
pm2 save

# View logs
pm2 logs portfolio

Nginx Configuration

Create Nginx Config

bash
sudo nano /etc/nginx/sites-available/portfolio

Add configuration:

nginx
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    # Increase max body size for uploads
    client_max_body_size 10M;
}

Enable Configuration

bash
# Enable site
sudo ln -s /etc/nginx/sites-available/portfolio /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

SSL Certificate

Install Certbot

bash
# Install Certbot
sudo apt install -y certbot python3-certbot-nginx

# Obtain certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Follow the prompts

Auto-Renewal

Certbot sets up auto-renewal automatically. Test it:

bash
sudo certbot renew --dry-run

Deployment Updates

Manual Updates

bash
cd /var/www/portfolio

# Pull latest changes
git pull origin main

# Install new dependencies
pnpm install

# Run migrations (if any)
pnpm payload migrate

# Rebuild
pnpm build

# Restart PM2
pm2 restart portfolio

Automated Deployment Script

Create deploy.sh:

bash
#!/bin/bash
set -e

cd /var/www/portfolio

echo "Pulling latest changes..."
git pull origin main

echo "Installing dependencies..."
pnpm install

echo "Running migrations..."
pnpm payload migrate

echo "Building application..."
pnpm build

echo "Restarting PM2..."
pm2 restart portfolio

echo "Deployment complete!"

Make executable:

bash
chmod +x deploy.sh

GitHub Actions Deployment

Create .github/workflows/deploy.yml:

yaml
name: Deploy to VPS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/portfolio
            ./deploy.sh

Monitoring

PM2 Monitoring

bash
# View process list
pm2 list

# View logs
pm2 logs portfolio

# Monitor resources
pm2 monit

# View detailed info
pm2 show portfolio

System Monitoring

bash
# Install htop
sudo apt install htop

# Monitor system
htop

Log Rotation

bash
# Install PM2 log rotation
pm2 install pm2-logrotate

# Configure
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

Security

Firewall Setup

bash
# Enable UFW
sudo ufw enable

# Allow SSH
sudo ufw allow ssh

# Allow HTTP/HTTPS
sudo ufw allow 'Nginx Full'

# Check status
sudo ufw status

Fail2ban

bash
# Install fail2ban
sudo apt install -y fail2ban

# Start and enable
sudo systemctl start fail2ban
sudo systemctl enable fail2ban

Security Headers

Add to Nginx config:

nginx
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Troubleshooting

Application Won't Start

bash
# Check PM2 logs
pm2 logs portfolio --lines 100

# Check for port conflicts
sudo lsof -i :3000

# Verify environment variables
cat .env

Database Connection Issues

bash
# Check PostgreSQL status
sudo systemctl status postgresql

# Test connection
psql -U portfolio -d portfoliocms -h localhost

Nginx Errors

bash
# Check Nginx error log
sudo tail -f /var/log/nginx/error.log

# Test configuration
sudo nginx -t

SSL Certificate Issues

bash
# Check certificate status
sudo certbot certificates

# Force renewal
sudo certbot renew --force-renewal

Backup Strategy

Database Backup

bash
# Create backup
pg_dump -U portfolio portfoliocms > backup_$(date +%Y%m%d).sql

# Automate with cron
crontab -e
# Add: 0 2 * * * pg_dump -U portfolio portfoliocms > /backups/db_$(date +\%Y\%m\%d).sql

Application Backup

bash
# Backup entire app
tar -czvf portfolio_backup.tar.gz /var/www/portfolio

Released under the MIT License.