DigitalOcean App Platform Deployment
Comprehensive deployment guide for the MyNATCA ecosystem on DigitalOcean App Platform, including multi-service configuration, Redis setup, and production best practices.
Deployment Architecture Overview
The MyNATCA ecosystem is deployed using Docker containers on DigitalOcean infrastructure with Express.js backend services:
Docker Configuration
Platform Service Dockerfile
The platform uses a production-optimized Dockerfile with Node.js 18 Alpine:
# MyNATCA Platform - Production Docker Configuration
FROM node:18-alpine
# Set the working directory
WORKDIR /app
# Copy package files first (for better caching)
COPY package*.json ./
# Install production dependencies only
RUN npm ci --only=production && npm cache clean --force
# Create a non-root user for security
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
# Copy the rest of the application
COPY . .
# Create logs directory and set permissions
RUN mkdir -p logs && chown -R nodejs:nodejs /app
# Switch to non-root user
USER nodejs
# Expose the port the app runs on
EXPOSE 3000
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/api/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"
# Start the application
CMD ["npm", "start"]PM2 Production Configuration
The platform uses PM2 for process management in cluster mode:
// ecosystem.config.js - PM2 Production Configuration
module.exports = {
apps: [{
name: 'mynatca-platform',
script: 'server.js',
cwd: '/opt/mynatca-platform',
instances: 'max', // Use all available CPU cores
exec_mode: 'cluster',
// Environment configuration
env: {
NODE_ENV: 'production',
PORT: 3000,
HOST: '0.0.0.0'
},
// Logging configuration
log_file: './logs/combined.log',
out_file: './logs/out.log',
error_file: './logs/error.log',
time: true,
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
// Memory and performance
max_memory_restart: '500M',
node_args: '--max_old_space_size=460',
// Process management
min_uptime: '10s',
max_restarts: 5,
autorestart: true,
// Monitoring
monitor: true,
// Health check
health_check_grace_period: 3000
}]
};Production Deployment Process
Prerequisites
DigitalOcean Requirements
- Droplet: Minimum 2GB RAM, 1 vCPU (recommended: 4GB RAM, 2 vCPU)
- Operating System: Ubuntu 22.04 LTS
- Storage: 50GB SSD minimum
- Networking: Firewall configured for ports 80, 443, 22
External Services
- Supabase Project: PostgreSQL database with migrations applied
- Auth0 Tenant: Server-side authentication configuration
- Redis Instance: Session storage with password authentication
- MySQL Access: Read-only access to MyNATCA production database
Server Setup
# Update system and install Node.js 18.x
sudo apt update && sudo apt upgrade -y
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install Docker and Docker Compose
sudo apt install docker.io docker-compose -y
sudo systemctl enable docker
sudo usermod -aG docker $USER
# Install Nginx for reverse proxy
sudo apt install nginx -y
sudo systemctl enable nginx
# Install PM2 for process management
sudo npm install -g pm2Application Deployment
# Clone and setup application
git clone <repository-url> /opt/mynatca-platform
cd /opt/mynatca-platform
# Install production dependencies
npm ci --only=production
# Copy environment configuration
cp .env.production.example .env.production
# Build Docker image
docker build -t mynatca-platform:latest .
# Start with PM2 cluster mode
pm2 start ecosystem.config.js --env production
# Save PM2 configuration
pm2 save
pm2 startupEnvironment Variables
Required Production Configuration
# Server Configuration
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
# Database Configuration
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
# Authentication
AUTH0_DOMAIN=mynatca.auth0.com
AUTH0_CLIENT_ID=your_client_id
AUTH0_CLIENT_SECRET=your_client_secret
AUTH0_REDIRECT_URI=https://platform.natca.org/api/auth/callback
AUTH0_BASE_URL=https://platform.natca.org
# Session Management
SESSION_SECRET=your_secure_session_secret_64_chars_minimum
REDIS_URL=redis://username:password@redis-host:6379
# Service Keys
SYNC_API_KEY=secure_sync_api_key
DISCORD_SERVICE_KEY=discord_bot_service_key
# External Services
DISCORD_BOT_URL=https://discord.natca.orgApp Platform Configuration
Complete App Specification
# .do/app.yaml - DigitalOcean App Platform specification
name: mynatca-ecosystem
region: nyc
# Platform Service Configuration
services:
- name: platform
source_dir: /
dockerfile_path: Dockerfile
github:
repo: natca/mynatca-platform
branch: main
run_command: npm start
environment_slug: node-js
instance_count: 2
instance_size_slug: professional-xs
# Health check configuration
health_check:
http_path: /api/health
initial_delay_seconds: 30
period_seconds: 10
timeout_seconds: 5
success_threshold: 1
failure_threshold: 3
# Auto-scaling configuration
autoscaling:
min_instance_count: 1
max_instance_count: 3
metrics:
cpu:
percent: 80
# Environment Variables
envs:
- key: NODE_ENV
value: production
type: GENERAL
- key: PORT
value: "3000"
type: GENERAL
value: ${supabase.url}
type: SECRET
- key: SUPABASE_ANON_KEY
value: ${supabase.anon_key}
type: SECRET
- key: SUPABASE_SERVICE_ROLE_KEY
value: ${supabase.service_role_key}
type: SECRET
- key: AUTH0_DOMAIN
value: ${auth0.domain}
type: SECRET
- key: AUTH0_CLIENT_ID
value: ${auth0.client_id}
type: SECRET
- key: AUTH0_CLIENT_SECRET
value: ${auth0.client_secret}
type: SECRET
- key: AUTH0_AUDIENCE
value: ${auth0.audience}
type: SECRET
- key: REDIS_URL
value: ${redis.url}
type: SECRET
- key: MYSQL_HOST
value: ${mysql.host}
type: SECRET
- key: MYSQL_USER
value: ${mysql.user}
type: SECRET
- key: MYSQL_PASSWORD
value: ${mysql.password}
type: SECRET
- key: MYSQL_DATABASE
value: ${mysql.database}
type: SECRET
services:
# MyNATCA Platform (Backend API & Proxy)
- name: platform
source_dir: /platform
github:
repo: yourusername/mynatca-platform
branch: main
deploy_on_push: true
build_command: |
echo "Building MyNATCA Platform..."
npm ci --production=false
echo "Platform build complete"
run_command: |
echo "Starting MyNATCA Platform..."
npm start
environment_slug: node-js
instance_count: 2
instance_size_slug: professional-xs
http_port: 1300
health_check:
http_path: /api/health
initial_delay_seconds: 60
period_seconds: 10
success_threshold: 1
failure_threshold: 3
timeout_seconds: 5
envs:
- key: PORT
value: "1300"
type: GENERAL
- key: SESSION_SECRET
value: ${session.secret}
type: SECRET
- key: PLATFORM_SERVICE_NAME
value: "mynatca-platform"
type: GENERAL
alerts:
- rule: CPU_UTILIZATION
value: 75
operator: GREATER_THAN
- rule: MEM_UTILIZATION
value: 80
operator: GREATER_THAN
# MyNATCA Hub (Frontend Vue.js Application)
- name: hub
source_dir: /hub
github:
repo: yourusername/mynatca-hub
branch: main
deploy_on_push: true
build_command: |
echo "Building MyNATCA Hub..."
npm ci --production=false
npm run typecheck
npm run build
echo "Hub build complete"
run_command: |
echo "Starting MyNATCA Hub preview server..."
npm run preview
environment_slug: node-js
instance_count: 2
instance_size_slug: professional-xs
http_port: 1301
health_check:
http_path: /
initial_delay_seconds: 60
period_seconds: 10
success_threshold: 1
failure_threshold: 3
timeout_seconds: 5
envs:
- key: PORT
value: "1301"
type: GENERAL
- key: VITE_SUPABASE_URL
value: ${supabase.url}
type: SECRET
- key: VITE_SUPABASE_ANON_KEY
value: ${supabase.anon_key}
type: SECRET
- key: VITE_AUTH0_DOMAIN
value: ${auth0.domain}
type: SECRET
- key: VITE_AUTH0_CLIENT_ID
value: ${auth0.client_id}
type: SECRET
- key: VITE_PLATFORM_URL
value: "https://${APP_DOMAIN}/api"
type: GENERAL
routes:
- path: /hub
preserve_path_prefix: true
alerts:
- rule: CPU_UTILIZATION
value: 75
operator: GREATER_THAN
# MyNATCA Discord Bot
- name: discord-bot
source_dir: /discord
github:
repo: yourusername/mynatca-discord
branch: main
deploy_on_push: true
build_command: |
echo "Building MyNATCA Discord Bot..."
npm ci --production=false
echo "Discord bot build complete"
run_command: |
echo "Starting MyNATCA Discord Bot..."
npm start
environment_slug: node-js
instance_count: 1
instance_size_slug: basic-xxs
envs:
- key: DISCORD_TOKEN
value: ${discord.token}
type: SECRET
- key: DISCORD_CLIENT_ID
value: ${discord.client_id}
type: SECRET
- key: DISCORD_CLIENT_SECRET
value: ${discord.client_secret}
type: SECRET
- key: GUILD_ID
value: ${discord.guild_id}
type: SECRET
- key: PLATFORM_URL
value: "https://${APP_DOMAIN}/api"
type: GENERAL
alerts:
- rule: CPU_UTILIZATION
value: 85
operator: GREATER_THAN
# Database Configuration (References to external services)
databases: []
# Ingress Configuration
ingress:
rules:
- match:
path:
prefix: /api
component:
name: platform
- match:
path:
prefix: /hub
component:
name: hub
- match:
path:
prefix: /
component:
name: platform
load_balancer:
algorithm: round_robin
sticky_sessions:
type: cookies
cookie_name: PLATFORM_SESSION
cookie_ttl_seconds: 3600
# Domain Configuration
domains:
- domain: mynatca-hub.com
type: PRIMARY
minimum_tls_version: "1.2"
certificate:
type: LETS_ENCRYPT
- domain: platform.mynatca-hub.com
type: ALIAS
minimum_tls_version: "1.2"
certificate:
type: LETS_ENCRYPT
# Static Sites (if any)
static_sites: []
# Background Jobs/Workers
jobs: []Redis Database Setup
Managed Redis Configuration
#!/bin/bash
# .do/provision-redis.sh - Automated Redis provisioning
set -e
# Configuration
REDIS_CLUSTER_NAME="mynatca-redis"
REDIS_REGION="nyc3"
REDIS_SIZE="db-s-1vcpu-2gb" # Production recommendation
REDIS_NODES=1 # Single node for cost efficiency
REDIS_VERSION="7"
echo "🔧 Provisioning MyNATCA Redis cluster..."
# Create Redis cluster
echo "📦 Creating Redis cluster: $REDIS_CLUSTER_NAME"
doctl databases create redis $REDIS_CLUSTER_NAME \
--engine redis \
--version $REDIS_VERSION \
--size $REDIS_SIZE \
--region $REDIS_REGION \
--num-nodes $REDIS_NODES \
--wait
# Get cluster information
echo "🔍 Retrieving cluster information..."
CLUSTER_ID=$(doctl databases list --format ID,Name --no-header | grep $REDIS_CLUSTER_NAME | awk '{print $1}')
if [ -z "$CLUSTER_ID" ]; then
echo "❌ Failed to retrieve cluster ID"
exit 1
fi
echo "✅ Redis cluster created successfully!"
echo "📋 Cluster ID: $CLUSTER_ID"
# Get connection details
echo "🔗 Retrieving connection details..."
CONNECTION_INFO=$(doctl databases connection $CLUSTER_ID --format URI --no-header)
echo "✅ Redis cluster ready for use!"
echo "🔗 Connection String: $CONNECTION_INFO"
echo ""
echo "📝 Next Steps:"
echo " 1. Add REDIS_URL environment variable to your App Platform config"
echo " 2. Update app.yaml with the connection string"
echo " 3. Deploy your application"
echo ""
echo "💰 Estimated Monthly Cost: $25 USD (db-s-1vcpu-2gb)"Redis Sizing Recommendations
| Use Case | Size | Monthly Cost | Recommendation |
|---|---|---|---|
| Development | db-s-1vcpu-1gb | $15 | Testing and dev environments |
| Production (Recommended) | db-s-1vcpu-2gb | $25 | Main production deployment |
| High Availability | db-s-2vcpu-4gb (2 nodes) | $100 | Critical production with failover |
| Enterprise | db-s-4vcpu-8gb (3 nodes) | $300 | High-traffic, mission-critical |
Automated Deployment
Deployment Script
#!/bin/bash
# .do/deploy.sh - Automated deployment script
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
APP_NAME="mynatca-ecosystem"
REGION="nyc3"
echo -e "${BLUE}🚀 MyNATCA Ecosystem Deployment${NC}"
echo "========================================="
# Pre-flight checks
echo -e "${YELLOW}🔍 Running pre-flight checks...${NC}"
# Check if doctl is installed
if ! command -v doctl &> /dev/null; then
echo -e "${RED}❌ doctl CLI is not installed${NC}"
echo " Install: https://docs.digitalocean.com/reference/doctl/how-to/install/"
exit 1
fi
# Check authentication
if ! doctl auth list &> /dev/null; then
echo -e "${RED}❌ Please authenticate with DigitalOcean first${NC}"
echo " Run: doctl auth init"
exit 1
fi
# Check required files
for file in ".do/app.yaml" "platform/package.json" "hub/package.json"; do
if [ ! -f "$file" ]; then
echo -e "${RED}❌ Required file not found: $file${NC}"
exit 1
fi
done
echo -e "${GREEN}✅ Pre-flight checks passed${NC}"
# Environment variables check
echo -e "${YELLOW}🔐 Checking environment variables...${NC}"
required_vars=(
"SUPABASE_URL"
"SUPABASE_ANON_KEY"
"SUPABASE_SERVICE_ROLE_KEY"
"AUTH0_DOMAIN"
"AUTH0_CLIENT_ID"
"AUTH0_CLIENT_SECRET"
"AUTH0_AUDIENCE"
"REDIS_URL"
"SESSION_SECRET"
"DISCORD_TOKEN"
"DISCORD_CLIENT_ID"
"DISCORD_CLIENT_SECRET"
"GUILD_ID"
)
missing_vars=()
for var in "${required_vars[@]}"; do
if [ -z "${!var}" ]; then
missing_vars+=("$var")
fi
done
if [ ${#missing_vars[@]} -ne 0 ]; then
echo -e "${YELLOW}⚠️ Missing environment variables:${NC}"
printf ' - %s\n' "${missing_vars[@]}"
echo -e "${YELLOW} Configure these in DigitalOcean App Platform dashboard${NC}"
fi
# Deploy application
echo -e "${YELLOW}🚀 Deploying application...${NC}"
if doctl apps list --format Name --no-header | grep -q "^${APP_NAME}$"; then
echo -e "${YELLOW}📱 App '${APP_NAME}' exists, updating...${NC}"
APP_ID=$(doctl apps list --format ID,Name --no-header | grep "${APP_NAME}" | awk '{print $1}')
doctl apps update ${APP_ID} --spec .do/app.yaml
echo -e "${GREEN}✅ App updated successfully!${NC}"
else
echo -e "${BLUE}🆕 Creating new app '${APP_NAME}'...${NC}"
APP_OUTPUT=$(doctl apps create --spec .do/app.yaml --format ID,Name,DefaultIngress --no-header)
APP_ID=$(echo "$APP_OUTPUT" | awk '{print $1}')
APP_URL=$(echo "$APP_OUTPUT" | awk '{print $3}')
echo -e "${GREEN}✅ App created successfully!${NC}"
echo -e "${BLUE}📋 App ID: ${APP_ID}${NC}"
echo -e "${BLUE}🌐 App URL: https://${APP_URL}${NC}"
fi
# Monitor deployment
echo -e "${YELLOW}⏳ Monitoring deployment...${NC}"
DEPLOYMENT_ID=""
while true; do
if [ -z "$DEPLOYMENT_ID" ]; then
DEPLOYMENT_ID=$(doctl apps list-deployments ${APP_ID} --format ID --no-header | head -n1)
fi
if [ -n "$DEPLOYMENT_ID" ]; then
STATUS=$(doctl apps get-deployment ${APP_ID} ${DEPLOYMENT_ID} --format Phase --no-header)
echo -e "${BLUE} Status: ${STATUS}${NC}"
case "$STATUS" in
"ACTIVE")
echo -e "${GREEN}✅ Deployment completed successfully!${NC}"
break
;;
"ERROR"|"SUPERSEDED")
echo -e "${RED}❌ Deployment failed: ${STATUS}${NC}"
exit 1
;;
*)
sleep 30
;;
esac
else
sleep 10
fi
done
echo -e "${GREEN}🎉 MyNATCA Ecosystem deployed successfully!${NC}"
echo -e "${BLUE}🎛️ Dashboard: https://cloud.digitalocean.com/apps/${APP_ID}${NC}"CI/CD Integration
# .github/workflows/deploy.yml - GitHub Actions deployment
name: Deploy to DigitalOcean App Platform
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
REGISTRY: registry.digitalocean.com
IMAGE_NAME: mynatca-platform
jobs:
deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install doctl
uses: digitalocean/action-doctl@v2
with:
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
- name: Update app specification
run: |
# Replace environment variable placeholders
sed -i 's/${supabase.url}/${{ secrets.SUPABASE_URL }}/g' .do/app.yaml
sed -i 's/${auth0.domain}/${{ secrets.AUTH0_DOMAIN }}/g' .do/app.yaml
# ... other replacements
- name: Deploy to App Platform
run: |
APP_ID=$(doctl apps list --format ID,Name --no-header | grep mynatca-ecosystem | awk '{print $1}')
if [ -n "$APP_ID" ]; then
doctl apps update $APP_ID --spec .do/app.yaml
else
doctl apps create --spec .do/app.yaml
fi
- name: Wait for deployment
run: |
APP_ID=$(doctl apps list --format ID,Name --no-header | grep mynatca-ecosystem | awk '{print $1}')
doctl apps get $APP_ID --waitEnvironment Management
Development Environment
# .env.development
NODE_ENV=development
PORT=1300
# Database
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
# Auth
AUTH0_DOMAIN=your-domain.auth0.com
AUTH0_CLIENT_ID=your_client_id
AUTH0_CLIENT_SECRET=your_client_secret
AUTH0_AUDIENCE=your_audience
# Session
SESSION_SECRET=your_long_random_secret_key_here
REDIS_URL=redis://localhost:6379/0
# Frontend
VITE_PLATFORM_URL=http://localhost:1300/api
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your_anon_key
VITE_AUTH0_DOMAIN=your-domain.auth0.com
VITE_AUTH0_CLIENT_ID=your_client_idProduction Environment Variables
Environment variables in DigitalOcean App Platform dashboard:
| Variable | Type | Description | Example |
|---|---|---|---|
NODE_ENV | General | Application environment | production |
SUPABASE_URL | Secret | Supabase project URL | https://abc.supabase.co |
SUPABASE_SERVICE_ROLE_KEY | Secret | Supabase service role key | eyJhbGciOiJI... |
AUTH0_DOMAIN | Secret | Auth0 tenant domain | mynatca.auth0.com |
AUTH0_CLIENT_SECRET | Secret | Auth0 application secret | abc123... |
REDIS_URL | Secret | Redis connection string | redis://user:pass@host:port/db |
SESSION_SECRET | Secret | Session encryption key | 256-bit random key |
DISCORD_TOKEN | Secret | Discord bot token | ODc2... |
Monitoring and Scaling
Health Checks
// Platform: /api/health endpoint
app.get('/api/health', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
service: 'mynatca-platform',
version: process.env.npm_package_version,
environment: process.env.NODE_ENV,
dependencies: {}
};
try {
// Check Redis connectivity
await redisClient.ping();
health.dependencies.redis = 'healthy';
} catch (error) {
health.dependencies.redis = 'unhealthy';
health.status = 'degraded';
}
try {
// Check Supabase connectivity
const { data, error } = await supabase
.from('proxy_routes')
.select('count')
.limit(1);
health.dependencies.supabase = error ? 'unhealthy' : 'healthy';
if (error) health.status = 'degraded';
} catch (error) {
health.dependencies.supabase = 'unhealthy';
health.status = 'degraded';
}
const statusCode = health.status === 'healthy' ? 200 : 503;
res.status(statusCode).json(health);
});Auto-scaling Configuration
# App Platform auto-scaling alerts
alerts:
- rule: CPU_UTILIZATION
value: 75
operator: GREATER_THAN
window: FIVE_MINUTES
- rule: MEM_UTILIZATION
value: 80
operator: GREATER_THAN
window: FIVE_MINUTES
- rule: RESTART_COUNT
value: 10
operator: GREATER_THAN
window: ONE_HOURPerformance Monitoring
// Platform: Performance middleware
const performanceMonitoring = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
const logData = {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration,
userAgent: req.get('user-agent'),
ip: req.ip,
timestamp: new Date().toISOString()
};
// Log slow requests
if (duration > 5000) {
console.warn('Slow request detected:', logData);
}
// Log errors
if (res.statusCode >= 400) {
console.error('Error request:', logData);
}
});
next();
};
app.use(performanceMonitoring);Cost Optimization
Resource Sizing
| Service | Instance Type | Count | Monthly Cost | Use Case |
|---|---|---|---|---|
| Platform | Professional-XS | 2 | ~$50 | API gateway with session management |
| Hub | Professional-XS | 2 | ~$50 | Vue.js frontend with build process |
| Discord Bot | Basic-XXS | 1 | ~$5 | Lightweight Discord integration |
| Redis | db-s-1vcpu-2gb | 1 | ~$25 | Session storage |
| Total | ~$130 | Production deployment |
Cost Reduction Strategies
- Development Environment: Use Basic instances
- Single Node Redis: Avoid cluster for non-critical applications
- Horizontal Scaling: Start with 1 instance per service
- Monitor Usage: Use DigitalOcean monitoring to right-size
Estimated Costs by Scale
| Scale | Instances | Redis | Total Monthly |
|---|---|---|---|
| Starter | 3x Basic | Single node | ~$45 |
| Production | 5x Professional-XS | Single node | ~$130 |
| Enterprise | 8x Professional-S | HA cluster | ~$400 |
Troubleshooting Deployment
Common Issues
Build Failures
# Check build logs
doctl apps logs <APP_ID> --type=build
# Common solutions
# 1. Clear node_modules in build command
# 2. Specify Node.js version
# 3. Check dependency versionsEnvironment Variable Issues
# List current environment variables
doctl apps get <APP_ID> --format Spec
# Update environment variables
doctl apps update <APP_ID> --spec .do/app.yamlDatabase Connection Issues
# Test Redis connectivity
doctl databases connection <REDIS_CLUSTER_ID>
# Check connection string format
redis://username:password@host:port/databaseService Communication Issues
# Check ingress routing
doctl apps get <APP_ID> --format Spec
# Verify service endpoints
curl -v https://your-app.ondigitalocean.app/api/healthThis deployment configuration provides a robust, scalable foundation for the MyNATCA ecosystem on DigitalOcean App Platform with proper monitoring, auto-scaling, and cost optimization.