Files
peters-portfolio-site/DEPLOYMENT.md
T

6.9 KiB

Deployment Guide

Complete setup for deploying the portfolio site with Docker, Caddy, and Oracle Cloud VPS.

Architecture

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Cloudflare    │────▶│   Oracle VPS    │────▶│   Vite Dev      │
│   (DNS + Proxy) │     │   (Caddy)       │     │   Server        │
│                 │     │   Port 80/443   │────▶│   Port 5173     │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                               │
                               ▼
                        ┌─────────────────┐
                        │   Docker        │
                        │   (Caddy)       │
                        └─────────────────┘

Prerequisites

  • Oracle Cloud VPS (or similar)
  • Domain name (Cloudflare recommended)
  • Docker + Docker Compose installed
  • Git access to repository

Server Setup

1. Provision VPS

Oracle Cloud Free Tier:

  1. Create instance (Ubuntu 22.04/24.04 ARM or AMD)
  2. Add ingress rules for ports 22, 80, 443
  3. Note the public IP

Open Ports in Oracle Console:

  1. Networking → Virtual Cloud Networks
  2. Click your VCN → Subnets
  3. Security Lists → Default Security List
  4. Add Ingress Rules:
    • TCP 80 from 0.0.0.0/0
    • TCP 443 from 0.0.0.0/0
    • TCP 22 from your IP (SSH)

2. Install Dependencies

# Update system
sudo apt update && sudo apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

# Install Node.js 22
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs

# Verify
node -v  # Should be 20.19+ or 22.12+
npm -v
docker --version

3. Clone Repository

sudo mkdir -p /opt/peters-portfolio-site
sudo chown $USER:$USER /opt/peters-portfolio-site
cd /opt/peters-portfolio-site
git clone https://git.dustin.coffee/hobokenchicken/peters-portfolio-site.git .

4. Configure Caddy

Create directory structure:

mkdir -p docker_configs/caddy/conf
mkdir -p docker_configs/caddy/site
mkdir -p docker_configs/caddy/caddy_data
mkdir -p docker_configs/caddy/caddy_config
mkdir -p docker_configs/caddy/logs

Create docker_configs/caddy/conf/Caddyfile:

pwlmyers.org {
    reverse_proxy 172.20.1.232:5173
    
    log {
        output file /var/log/caddy/access.log
        format json
    }
    
    # Optional: Add headers
    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        X-XSS-Protection "1; mode=block"
    }
}

Note: Adjust 172.20.1.232 to match your Vite server's IP (check with ip addr show)

5. Docker Compose for Caddy

Create compose.caddy.yaml:

services:
  caddy:
    image: caddy:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./docker_configs/caddy/conf:/etc/caddy
      - ./docker_configs/caddy/site:/srv
      - ./docker_configs/caddy/caddy_data:/data
      - ./docker_configs/caddy/caddy_config:/config
      - ./docker_configs/caddy/logs:/var/log/caddy
    networks:
      - caddy_network

networks:
  caddy_network:
    driver: bridge

6. DNS Configuration (Cloudflare)

  1. Log into Cloudflare dashboard

  2. Select your domain

  3. DNS tab → Add records:

    • Type: A
    • Name: @ (root) or www
    • Content: 150.136.209.11 (your VPS IP)
    • Proxy status: 🟠 Orange cloud (proxied) or Gray cloud (DNS only)
    • TTL: Auto
  4. SSL/TLS tab:

    • Mode: Full (strict) or Full
    • Always Use HTTPS: On

7. Deploy Application

cd /opt/peters-portfolio-site

# Copy docs to public
cp -r docs/* public/docs/

# Install dependencies
npm install

# Start Vite dev server (background)
nohup npm run dev > vite.log 2>&1 &

# Start Caddy
docker compose -f compose.caddy.yaml up -d

8. Verify Deployment

# Check Caddy is running
docker ps | grep caddy

# Check Vite is running
ps aux | grep vite
curl http://172.20.1.232:5173/

# Test from VPS
curl -H "Host: pwlmyers.org" http://localhost/

# Test HTTPS (after DNS propagates)
curl -v https://pwlmyers.org/

Updates

Pull and Update

cd /opt/peters-portfolio-site

# Pull latest changes
git pull

# Reinstall if package.json changed
npm install

# Copy any new docs
cp -r docs/* public/docs/

# Restart Vite
pkill -f vite || true
nohup npm run dev > vite.log 2>&1 &

# Restart Caddy if config changed
docker compose -f compose.caddy.yaml restart

View Logs

# Vite logs
tail -f vite.log

# Caddy logs
docker logs -f ubuntu-caddy-1
tail -f docker_configs/caddy/logs/access.log

Troubleshooting

SSL Certificate Issues

If Caddy fails to get Let's Encrypt certificate:

# Check Cloudflare proxy status
dig pwlmyers.org +short
# Should return your VPS IP, not Cloudflare IPs

# If using Cloudflare proxy (orange cloud):
# - Use Cloudflare Origin Certificate
# - Or switch to DNS only (gray cloud) for Let's Encrypt

Port Already in Use

# Find what's using port 80/443
sudo netstat -tlnp | grep -E ':(80|443)'

# Stop conflicting service
sudo systemctl stop nginx apache2

Vite Not Accessible

# Check Vite is listening on all interfaces
netstat -tlnp | grep 5173

# Should show 0.0.0.0:5173, not 127.0.0.1:5173

# Check allowedHosts in vite.config.ts
cat vite.config.ts

Docker Network Issues

# Check Caddy can reach Vite
docker exec ubuntu-caddy-1 wget -qO- http://172.20.1.232:5173/

# If using Docker network instead of host IP:
docker network ls
docker network inspect caddy_caddy_network

Alternative: Systemd Service

Instead of nohup, create a systemd service for Vite:

sudo tee /etc/systemd/system/peters-portfolio.service > /dev/null <<'EOF'
[Unit]
Description=Peter's Portfolio Vite Dev Server
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/peters-portfolio-site
ExecStart=/usr/bin/npm run dev
Restart=always
RestartSec=10
Environment=NODE_ENV=development

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable peters-portfolio
sudo systemctl start peters-portfolio
sudo systemctl status peters-portfolio

Security Notes

  • Keep node_modules updated: npm audit fix
  • Use Cloudflare proxy for DDoS protection
  • Consider fail2ban for SSH brute force protection
  • Regular backups of docs/ directory

Support

For issues with:

  • Application code: Check README.md troubleshooting section
  • Server/Docker: Check logs with commands above
  • DNS/SSL: Verify Cloudflare settings and Oracle firewall rules