feat: add Digital Ocean deployment configuration
- Create docker-compose.digitalocean.yml for registry-based deployment - Add .env.digitalocean.example template for cloud deployment - Add comprehensive DIGITAL_OCEAN_DEPLOYMENT.md guide - Configure image pulling from Gitea registry - Include SSL setup with Caddy/Traefik - Add backup, monitoring, and security instructions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
459
DIGITAL_OCEAN_DEPLOYMENT.md
Normal file
459
DIGITAL_OCEAN_DEPLOYMENT.md
Normal file
@@ -0,0 +1,459 @@
|
||||
# VIP Coordinator - Digital Ocean Deployment Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide walks you through deploying VIP Coordinator to Digital Ocean using pre-built Docker images from your Gitea registry.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [ ] Digital Ocean account
|
||||
- [ ] Docker images pushed to Gitea registry (completed ✅)
|
||||
- [ ] Domain name (recommended) or will use droplet IP
|
||||
- [ ] Auth0 account configured
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet
|
||||
│
|
||||
├─> Your Domain (optional)
|
||||
│
|
||||
↓
|
||||
[Digital Ocean Droplet]
|
||||
│
|
||||
├─> Caddy/Traefik (Reverse Proxy + SSL)
|
||||
│ ↓
|
||||
├─> Frontend Container (port 80)
|
||||
│ ↓
|
||||
├─> Backend Container (port 3000)
|
||||
│ ↓
|
||||
├─> PostgreSQL Container
|
||||
│ ↓
|
||||
└─> Redis Container
|
||||
```
|
||||
|
||||
## Step 1: Create Digital Ocean Droplet
|
||||
|
||||
### Recommended Specifications
|
||||
|
||||
**Minimum (Testing):**
|
||||
- **Size:** Basic Droplet - $12/month
|
||||
- **RAM:** 2GB
|
||||
- **CPU:** 1 vCPU
|
||||
- **Storage:** 50GB SSD
|
||||
- **Region:** Choose closest to your users
|
||||
|
||||
**Recommended (Production):**
|
||||
- **Size:** General Purpose - $24/month
|
||||
- **RAM:** 4GB
|
||||
- **CPU:** 2 vCPUs
|
||||
- **Storage:** 80GB SSD
|
||||
- **Region:** Choose closest to your users
|
||||
|
||||
### Create Droplet
|
||||
|
||||
1. Go to [Digital Ocean](https://cloud.digitalocean.com/droplets/new)
|
||||
2. **Choose Image:** Ubuntu 24.04 LTS x64
|
||||
3. **Choose Size:** Select based on recommendations above
|
||||
4. **Choose Region:** Select closest region
|
||||
5. **Authentication:** SSH keys (recommended) or password
|
||||
6. **Hostname:** `vip-coordinator`
|
||||
7. **Tags:** `production`, `vip-coordinator`
|
||||
8. **Backups:** Enable weekly backups (recommended)
|
||||
9. Click **Create Droplet**
|
||||
|
||||
## Step 2: Initial Server Setup
|
||||
|
||||
### SSH into Droplet
|
||||
|
||||
```bash
|
||||
ssh root@YOUR_DROPLET_IP
|
||||
```
|
||||
|
||||
### Update System
|
||||
|
||||
```bash
|
||||
apt update && apt upgrade -y
|
||||
```
|
||||
|
||||
### Create Non-Root User
|
||||
|
||||
```bash
|
||||
adduser vipcoord
|
||||
usermod -aG sudo vipcoord
|
||||
usermod -aG docker vipcoord # Will add docker group later
|
||||
```
|
||||
|
||||
### Configure Firewall (UFW)
|
||||
|
||||
```bash
|
||||
# Enable UFW
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
|
||||
# Allow SSH
|
||||
ufw allow OpenSSH
|
||||
|
||||
# Allow HTTP and HTTPS
|
||||
ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
|
||||
# Enable firewall
|
||||
ufw enable
|
||||
|
||||
# Check status
|
||||
ufw status
|
||||
```
|
||||
|
||||
## Step 3: Install Docker
|
||||
|
||||
```bash
|
||||
# Install Docker
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sh get-docker.sh
|
||||
|
||||
# Add user to docker group
|
||||
usermod -aG docker vipcoord
|
||||
|
||||
# Install Docker Compose
|
||||
apt install docker-compose-plugin -y
|
||||
|
||||
# Verify installation
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
## Step 4: Configure Gitea Registry Access
|
||||
|
||||
### Option A: Public Gitea (Recommended)
|
||||
|
||||
If your Gitea is publicly accessible:
|
||||
|
||||
```bash
|
||||
# Login to Gitea registry
|
||||
docker login YOUR_PUBLIC_GITEA_URL:3000 -u kyle
|
||||
# Enter your Gitea token: 2f4370ce710a4a1f84e8bf6c459fe63041376c0e
|
||||
```
|
||||
|
||||
### Option B: Gitea on LAN (Requires VPN/Tunnel)
|
||||
|
||||
If your Gitea is on LAN (192.168.68.53):
|
||||
|
||||
**Solutions:**
|
||||
1. **Tailscale VPN** (Recommended)
|
||||
- Install Tailscale on both your local machine and Digital Ocean droplet
|
||||
- Access Gitea via Tailscale IP
|
||||
|
||||
2. **SSH Tunnel**
|
||||
```bash
|
||||
# On your local machine
|
||||
ssh -L 3000:192.168.68.53:3000 root@YOUR_DROPLET_IP
|
||||
```
|
||||
|
||||
3. **Expose Gitea Publicly** (Not Recommended for Security)
|
||||
- Configure port forwarding on your router
|
||||
- Use dynamic DNS service
|
||||
- Set up Cloudflare tunnel
|
||||
|
||||
### Option C: Alternative - Push to Docker Hub
|
||||
|
||||
If Gitea access is complex, push images to Docker Hub instead:
|
||||
|
||||
```bash
|
||||
# On your local machine
|
||||
docker tag 192.168.68.53:3000/kyle/vip-coordinator/backend:latest kyle/vip-coordinator-backend:latest
|
||||
docker tag 192.168.68.53:3000/kyle/vip-coordinator/frontend:latest kyle/vip-coordinator-frontend:latest
|
||||
|
||||
docker push kyle/vip-coordinator-backend:latest
|
||||
docker push kyle/vip-coordinator-frontend:latest
|
||||
```
|
||||
|
||||
Then update `docker-compose.digitalocean.yml` to use Docker Hub images.
|
||||
|
||||
## Step 5: Deploy Application
|
||||
|
||||
### Copy Files to Droplet
|
||||
|
||||
```bash
|
||||
# On your local machine
|
||||
scp docker-compose.digitalocean.yml root@YOUR_DROPLET_IP:/home/vipcoord/
|
||||
scp .env.digitalocean.example root@YOUR_DROPLET_IP:/home/vipcoord/
|
||||
```
|
||||
|
||||
### Configure Environment
|
||||
|
||||
```bash
|
||||
# On droplet
|
||||
cd /home/vipcoord
|
||||
|
||||
# Copy and edit environment file
|
||||
cp .env.digitalocean.example .env.digitalocean
|
||||
nano .env.digitalocean
|
||||
```
|
||||
|
||||
**Update these values:**
|
||||
```env
|
||||
# If using LAN Gitea via Tailscale
|
||||
GITEA_REGISTRY=100.x.x.x:3000
|
||||
|
||||
# If using public Gitea
|
||||
GITEA_REGISTRY=gitea.yourdomain.com:3000
|
||||
|
||||
# If using Docker Hub
|
||||
# Comment out GITEA_REGISTRY and update image names in docker-compose
|
||||
|
||||
# Strong database password
|
||||
POSTGRES_PASSWORD=YOUR_STRONG_PASSWORD_HERE
|
||||
|
||||
# Auth0 configuration (same as before)
|
||||
AUTH0_DOMAIN=dev-s855cy3bvjjbkljt.us.auth0.com
|
||||
AUTH0_CLIENT_ID=JXEVOIfS5eYCkeKbbCWIkBYIvjqdSP5d
|
||||
AUTH0_AUDIENCE=https://vip-coordinator-api
|
||||
AUTH0_ISSUER=https://dev-s855cy3bvjjbkljt.us.auth0.com/
|
||||
```
|
||||
|
||||
### Start Services
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker compose -f docker-compose.digitalocean.yml --env-file .env.digitalocean up -d
|
||||
|
||||
# Check status
|
||||
docker compose -f docker-compose.digitalocean.yml ps
|
||||
|
||||
# View logs
|
||||
docker compose -f docker-compose.digitalocean.yml logs -f
|
||||
```
|
||||
|
||||
## Step 6: Set Up Reverse Proxy with SSL
|
||||
|
||||
### Option A: Caddy (Recommended - Easiest)
|
||||
|
||||
Create `Caddyfile`:
|
||||
|
||||
```bash
|
||||
nano Caddyfile
|
||||
```
|
||||
|
||||
```caddy
|
||||
your-domain.com {
|
||||
reverse_proxy localhost:80
|
||||
}
|
||||
```
|
||||
|
||||
Run Caddy:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name caddy \
|
||||
-p 80:80 \
|
||||
-p 443:443 \
|
||||
-v /home/vipcoord/Caddyfile:/etc/caddy/Caddyfile \
|
||||
-v caddy_data:/data \
|
||||
-v caddy_config:/config \
|
||||
--restart unless-stopped \
|
||||
caddy:latest
|
||||
```
|
||||
|
||||
Caddy automatically handles:
|
||||
- SSL certificate from Let's Encrypt
|
||||
- HTTP to HTTPS redirect
|
||||
- Certificate renewal
|
||||
|
||||
### Option B: Traefik
|
||||
|
||||
Create `docker-compose.traefik.yml`:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v2.10
|
||||
command:
|
||||
- "--api.insecure=true"
|
||||
- "--providers.docker=true"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.letsencrypt.acme.email=your@email.com"
|
||||
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
- "./letsencrypt:/letsencrypt"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
## Step 7: Configure Auth0
|
||||
|
||||
Update Auth0 application settings:
|
||||
|
||||
1. Go to [Auth0 Dashboard](https://manage.auth0.com/)
|
||||
2. Select your application
|
||||
3. **Allowed Callback URLs:** Add `https://your-domain.com`
|
||||
4. **Allowed Web Origins:** Add `https://your-domain.com`
|
||||
5. **Allowed Logout URLs:** Add `https://your-domain.com`
|
||||
6. Click **Save Changes**
|
||||
|
||||
## Step 8: Database Backups
|
||||
|
||||
### Automated Daily Backups
|
||||
|
||||
Create backup script:
|
||||
|
||||
```bash
|
||||
nano /home/vipcoord/backup-db.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/home/vipcoord/backups"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
docker exec vip-coordinator-postgres pg_dump -U vip_user vip_coordinator | gzip > $BACKUP_DIR/vip_coordinator_$TIMESTAMP.sql.gz
|
||||
|
||||
# Keep only last 7 days
|
||||
find $BACKUP_DIR -name "vip_coordinator_*.sql.gz" -mtime +7 -delete
|
||||
```
|
||||
|
||||
Make executable and add to cron:
|
||||
|
||||
```bash
|
||||
chmod +x /home/vipcoord/backup-db.sh
|
||||
|
||||
# Add to crontab (daily at 2 AM)
|
||||
crontab -e
|
||||
# Add this line:
|
||||
0 2 * * * /home/vipcoord/backup-db.sh
|
||||
```
|
||||
|
||||
## Step 9: Monitoring and Logging
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# All services
|
||||
docker compose -f docker-compose.digitalocean.yml logs -f
|
||||
|
||||
# Specific service
|
||||
docker compose -f docker-compose.digitalocean.yml logs -f backend
|
||||
|
||||
# Last 100 lines
|
||||
docker compose -f docker-compose.digitalocean.yml logs --tail=100 backend
|
||||
```
|
||||
|
||||
### Check Container Health
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
docker compose -f docker-compose.digitalocean.yml ps
|
||||
```
|
||||
|
||||
### Monitor Resources
|
||||
|
||||
```bash
|
||||
# Real-time resource usage
|
||||
docker stats
|
||||
|
||||
# Disk usage
|
||||
df -h
|
||||
docker system df
|
||||
```
|
||||
|
||||
## Step 10: Updating Application
|
||||
|
||||
When you push new images to Gitea:
|
||||
|
||||
```bash
|
||||
# On droplet
|
||||
cd /home/vipcoord
|
||||
|
||||
# Pull latest images
|
||||
docker compose -f docker-compose.digitalocean.yml pull
|
||||
|
||||
# Restart with new images
|
||||
docker compose -f docker-compose.digitalocean.yml down
|
||||
docker compose -f docker-compose.digitalocean.yml up -d
|
||||
|
||||
# Verify
|
||||
docker compose -f docker-compose.digitalocean.yml ps
|
||||
docker compose -f docker-compose.digitalocean.yml logs -f
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Application Not Accessible
|
||||
|
||||
1. Check firewall: `ufw status`
|
||||
2. Check containers: `docker ps`
|
||||
3. Check logs: `docker compose logs -f`
|
||||
4. Check Auth0 callback URLs match your domain
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
```bash
|
||||
# Check postgres is running
|
||||
docker exec vip-coordinator-postgres pg_isready -U vip_user
|
||||
|
||||
# Check backend can connect
|
||||
docker compose logs backend | grep -i database
|
||||
```
|
||||
|
||||
### SSL Certificate Issues
|
||||
|
||||
```bash
|
||||
# Caddy logs
|
||||
docker logs caddy
|
||||
|
||||
# Force certificate renewal
|
||||
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] Firewall configured (only 22, 80, 443 open)
|
||||
- [ ] SSH key authentication (disable password auth)
|
||||
- [ ] Non-root user for application
|
||||
- [ ] Strong database password
|
||||
- [ ] Auth0 callbacks restricted to production domain
|
||||
- [ ] Automated backups configured
|
||||
- [ ] SSL/TLS enabled
|
||||
- [ ] Regular system updates scheduled
|
||||
- [ ] Fail2ban installed for SSH protection
|
||||
- [ ] Docker containers run as non-root users
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
**Monthly Costs:**
|
||||
- Droplet (4GB): $24/month
|
||||
- Backups (20%): ~$5/month
|
||||
- **Total:** ~$29/month
|
||||
|
||||
**Optional:**
|
||||
- Domain name: $10-15/year
|
||||
- Digital Ocean Managed Database (if scaling): $15/month+
|
||||
|
||||
## Support
|
||||
|
||||
- **Digital Ocean Docs:** https://docs.digitalocean.com/
|
||||
- **Docker Docs:** https://docs.docker.com/
|
||||
- **Auth0 Docs:** https://auth0.com/docs
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Set up domain name (optional but recommended)
|
||||
2. Configure monitoring (Uptime Robot, etc.)
|
||||
3. Set up log aggregation (Digital Ocean Monitoring, Papertrail)
|
||||
4. Configure automated updates
|
||||
5. Add staging environment for testing
|
||||
|
||||
---
|
||||
|
||||
**Deployment completed!** 🚀
|
||||
|
||||
Your VIP Coordinator is now live at `https://your-domain.com`
|
||||
Reference in New Issue
Block a user