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.