2026-01-14 04:56:20 +00:00
|
|
|
# Multi-Provider Web App Workspace
|
|
|
|
|
|
|
|
|
|
This workspace demonstrates a production-ready web application deployment spanning three cloud providers:
|
|
|
|
|
|
|
|
|
|
- **DigitalOcean**: Web servers and load balancing (NYC region)
|
|
|
|
|
- **AWS**: Managed PostgreSQL database with high availability (US-East region)
|
|
|
|
|
- **Hetzner**: Backup storage and disaster recovery (Germany region)
|
|
|
|
|
|
|
|
|
|
## Why Three Providers?
|
|
|
|
|
|
|
|
|
|
This architecture optimizes cost, performance, and reliability:
|
|
|
|
|
|
|
|
|
|
- **DigitalOcean** (~$77/month): Cost-effective compute with simple management
|
|
|
|
|
- **AWS RDS** (~$75/month): Managed database with automatic failover
|
|
|
|
|
- **Hetzner** (~$13/month): Affordable backup storage
|
|
|
|
|
- **Total**: ~$165/month (vs $300+ for equivalent all-cloud setup)
|
|
|
|
|
|
|
|
|
|
## Architecture Overview
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
┌─────────────────────────────────────────────┐
|
|
|
|
|
│ Client Requests │
|
|
|
|
|
└──────────────┬──────────────────────────────┘
|
|
|
|
|
│ HTTPS/HTTP
|
|
|
|
|
┌───────▼─────────┐
|
|
|
|
|
│ DigitalOcean LB │
|
|
|
|
|
└───────┬─────────┘
|
|
|
|
|
┌────────┼────────┐
|
|
|
|
|
│ │ │
|
|
|
|
|
┌─▼──┐ ┌─▼──┐ ┌─▼──┐
|
|
|
|
|
│Web │ │Web │ │Web │ (DigitalOcean Droplets)
|
|
|
|
|
│ 1 │ │ 2 │ │ 3 │
|
|
|
|
|
└──┬─┘ └──┬─┘ └──┬─┘
|
|
|
|
|
│ │ │
|
|
|
|
|
└───────┼───────┘
|
|
|
|
|
│ VPN Tunnel
|
|
|
|
|
┌───────▼────────────┐
|
|
|
|
|
│ AWS RDS (PG) │ (us-east-1)
|
|
|
|
|
│ Multi-AZ Cluster │
|
|
|
|
|
└────────┬───────────┘
|
|
|
|
|
│ Replication
|
|
|
|
|
┌──────▼──────────┐
|
|
|
|
|
│ Hetzner Volume │ (nbg1 - Germany)
|
|
|
|
|
│ Backups │
|
|
|
|
|
└─────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Prerequisites
|
|
|
|
|
|
|
|
|
|
### 1. Cloud Accounts
|
|
|
|
|
|
|
|
|
|
- **DigitalOcean**: Account with API token
|
|
|
|
|
- **AWS**: Account with access keys
|
|
|
|
|
- **Hetzner**: Account with API token
|
|
|
|
|
|
|
|
|
|
### 2. Environment Variables
|
|
|
|
|
|
|
|
|
|
Set these before deployment:
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
export DIGITALOCEAN_TOKEN="dop_v1_abc123def456ghi789jkl012mno"
|
|
|
|
|
export AWS_ACCESS_KEY_ID="AKIA1234567890ABCDEF"
|
|
|
|
|
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG+j/zI0m1234567890ab"
|
|
|
|
|
export HCLOUD_TOKEN="MC4wNTI1YmE1M2E4YmE0YTQzMTQyZTdlODYy"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. SSH Key Setup
|
|
|
|
|
|
|
|
|
|
#### DigitalOcean
|
|
|
|
|
```bash
|
|
|
|
|
# Upload your SSH public key
|
|
|
|
|
doctl compute ssh-key create provisioning-key
|
|
|
|
|
--public-key-from-file ~/.ssh/id_rsa.pub
|
|
|
|
|
|
|
|
|
|
# Note the key ID for workspace.ncl
|
|
|
|
|
doctl compute ssh-key list
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### AWS
|
|
|
|
|
```bash
|
|
|
|
|
# Create EC2 key pair (if needed)
|
|
|
|
|
aws ec2 create-key-pair --key-name provisioning-key
|
|
|
|
|
--query 'KeyMaterial' --output text > provisioning-key.pem
|
|
|
|
|
chmod 600 provisioning-key.pem
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### Hetzner
|
|
|
|
|
```bash
|
|
|
|
|
# Upload SSH key
|
|
|
|
|
hcloud ssh-key create --name provisioning-key
|
|
|
|
|
--public-key-from-file ~/.ssh/id_rsa.pub
|
|
|
|
|
|
|
|
|
|
# List keys
|
|
|
|
|
hcloud ssh-key list
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4. DNS Setup
|
|
|
|
|
|
|
|
|
|
Update `workspace.ncl` with your domain:
|
|
|
|
|
- Replace `your-certificate-id` with actual AWS certificate ID
|
|
|
|
|
- Update load balancer CNAME to point to your domain
|
|
|
|
|
|
|
|
|
|
## Deployment
|
|
|
|
|
|
|
|
|
|
### Step 1: Configure the Workspace
|
|
|
|
|
|
|
|
|
|
Edit `workspace.ncl` to:
|
|
|
|
|
- Set your SSH key IDs
|
|
|
|
|
- Update certificate ID for HTTPS
|
|
|
|
|
- Set domain names
|
|
|
|
|
- Adjust instance counts if needed
|
|
|
|
|
|
|
|
|
|
Edit `config.toml` to:
|
|
|
|
|
- Set correct environment variable names
|
|
|
|
|
- Adjust thresholds and settings
|
|
|
|
|
|
|
|
|
|
### Step 2: Validate Configuration
|
|
|
|
|
|
|
|
|
|
```toml
|
|
|
|
|
# Validate Nickel syntax
|
|
|
|
|
nickel export workspace.ncl | jq .
|
|
|
|
|
|
|
|
|
|
# Validate provider credentials
|
|
|
|
|
provisioning provider verify digitalocean
|
|
|
|
|
provisioning provider verify aws
|
|
|
|
|
provisioning provider verify hetzner
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Step 3: Deploy
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Using provided deploy script
|
|
|
|
|
./deploy.nu
|
|
|
|
|
|
|
|
|
|
# Or manually via provisioning CLI
|
|
|
|
|
provisioning workspace deploy --config config.toml
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Step 4: Verify Deployment
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# List resources per provider
|
|
|
|
|
doctl compute droplet list
|
|
|
|
|
aws rds describe-db-instances
|
|
|
|
|
hcloud volume list
|
|
|
|
|
|
|
|
|
|
# Test load balancer
|
|
|
|
|
curl http://your-domain.com/health
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Post-Deployment Configuration
|
|
|
|
|
|
|
|
|
|
### 1. Application Deployment
|
|
|
|
|
|
|
|
|
|
SSH into web servers and deploy application:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Get web server IPs
|
|
|
|
|
doctl compute droplet list --format Name,PublicIPv4
|
|
|
|
|
|
|
|
|
|
# SSH to first server
|
|
|
|
|
ssh root@198.51.100.15
|
|
|
|
|
|
|
|
|
|
# Deploy application
|
|
|
|
|
cd /var/www
|
|
|
|
|
git clone https://github.com/your-org/web-app.git
|
|
|
|
|
cd web-app
|
|
|
|
|
./deploy.sh
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. Database Configuration
|
|
|
|
|
|
|
|
|
|
Connect to RDS database and initialize schema:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Get RDS endpoint
|
|
|
|
|
aws rds describe-db-instances --query 'DBInstances[0].Endpoint.Address'
|
|
|
|
|
|
|
|
|
|
# Connect and initialize
|
|
|
|
|
psql -h webapp-db.c9akciq32.us-east-1.rds.amazonaws.com -U admin -d defaultdb < schema.sql
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. DNS Configuration
|
|
|
|
|
|
|
|
|
|
Point your domain to the load balancer:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Get load balancer IP
|
|
|
|
|
doctl compute load-balancer list
|
|
|
|
|
|
|
|
|
|
# Update DNS CNAME
|
|
|
|
|
# Add CNAME record: app.example.com -> lb-123456789.nyc3.digitalocean.com
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4. SSL/TLS Certificate
|
|
|
|
|
|
|
|
|
|
Use AWS Certificate Manager:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Request certificate
|
|
|
|
|
aws acm request-certificate
|
|
|
|
|
--domain-name app.example.com
|
|
|
|
|
--validation-method DNS
|
|
|
|
|
|
|
|
|
|
# Validate and get certificate ID
|
|
|
|
|
aws acm list-certificates | grep app.example.com
|
|
|
|
|
|
|
|
|
|
# Update workspace.ncl with certificate ID
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Monitoring
|
|
|
|
|
|
|
|
|
|
### DigitalOcean Monitoring
|
|
|
|
|
|
|
|
|
|
- CPU usage tracked per droplet
|
|
|
|
|
- Memory usage alerts on Droplet greater than 85%
|
|
|
|
|
- Disk space alerts on greater than 90% full
|
|
|
|
|
|
|
|
|
|
### AWS CloudWatch
|
|
|
|
|
|
|
|
|
|
- RDS database metrics (CPU, connections, disk)
|
|
|
|
|
- Automatic failover notifications
|
|
|
|
|
- Slow query logging
|
|
|
|
|
|
|
|
|
|
### Hetzner Monitoring
|
|
|
|
|
|
|
|
|
|
- Volume usage tracking
|
|
|
|
|
- Manual monitoring script via cron
|
|
|
|
|
|
|
|
|
|
### Application Monitoring
|
|
|
|
|
|
|
|
|
|
Implement application-level monitoring:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# SSH to web server
|
|
|
|
|
ssh root@198.51.100.15
|
|
|
|
|
|
|
|
|
|
# Check app logs
|
|
|
|
|
tail -f /var/www/app/logs/application.log
|
|
|
|
|
|
|
|
|
|
# Monitor system resources
|
|
|
|
|
top
|
|
|
|
|
iostat -x 1
|
|
|
|
|
|
|
|
|
|
# Check database connection pool
|
|
|
|
|
psql -h webapp-db.c9akciq32.us-east-1.rds.amazonaws.com -c "SELECT count(plus) FROM pg_stat_activity;"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Backup and Recovery
|
|
|
|
|
|
|
|
|
|
### Automated Backups
|
|
|
|
|
|
|
|
|
|
- **RDS**: Daily backups retained for 30 days (AWS handles)
|
|
|
|
|
- **Application Data**: Weekly backups to Hetzner volume
|
|
|
|
|
- **Configuration**: Version control via Git
|
|
|
|
|
|
|
|
|
|
### Manual Backup
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Backup RDS to Hetzner volume
|
|
|
|
|
ssh hetzner-backup-volume
|
|
|
|
|
|
|
|
|
|
# Mount Hetzner volume (if not mounted)
|
|
|
|
|
sudo mount /dev/sdb /mnt/backups
|
|
|
|
|
|
|
|
|
|
# Backup RDS database
|
|
|
|
|
pg_dump -h webapp-db.c9akciq32.us-east-1.rds.amazonaws.com -U admin -d defaultdb |
|
|
|
|
|
gzip > /mnt/backups/db-$(date +%Y%m%d).sql.gz
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Recovery Procedure
|
|
|
|
|
|
|
|
|
|
1. **Web Server Failure**: Load balancer automatically redirects to healthy server
|
|
|
|
|
2. **Database Failure**: RDS Multi-AZ automatic failover
|
|
|
|
|
3. **Complete Failure**: Restore from Hetzner backup volume
|
|
|
|
|
|
|
|
|
|
## Scaling
|
|
|
|
|
|
|
|
|
|
### Add More Web Servers
|
|
|
|
|
|
|
|
|
|
Edit `workspace.ncl`:
|
|
|
|
|
|
|
|
|
|
```nickel
|
|
|
|
|
droplets = digitalocean.Droplet & {
|
|
|
|
|
name = "web-server",
|
|
|
|
|
region = "nyc3",
|
|
|
|
|
size = "s-2vcpu-4gb",
|
|
|
|
|
count = 5
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Redeploy:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./deploy.nu
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Upgrade Database
|
|
|
|
|
|
|
|
|
|
Edit `workspace.ncl`:
|
|
|
|
|
|
|
|
|
|
```nickel
|
|
|
|
|
database_tier = aws.RDS & {
|
|
|
|
|
identifier = "webapp-db",
|
|
|
|
|
instance_class = "db.t3.large"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Redeploy with minimal downtime (Multi-AZ handles switchover).
|
|
|
|
|
|
|
|
|
|
## Cost Optimization
|
|
|
|
|
|
|
|
|
|
### Reduce Costs
|
|
|
|
|
|
|
|
|
|
1. **Droplets**: Use smaller size or fewer instances
|
|
|
|
|
2. **Database**: Switch to smaller db.t3.small (approximately $30/month)
|
|
|
|
|
3. **Storage**: Reduce backup volume size
|
|
|
|
|
4. **Data Transfer**: Monitor and optimize outbound traffic
|
|
|
|
|
|
|
|
|
|
### Monitor Costs
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# DigitalOcean estimated bill
|
|
|
|
|
doctl billing get
|
|
|
|
|
|
|
|
|
|
# AWS Cost Explorer
|
|
|
|
|
aws ce get-cost-and-usage --time-period Start=2024-01-01,End=2024-01-31
|
|
|
|
|
|
|
|
|
|
# Hetzner manual tracking via console
|
|
|
|
|
# Navigate to https://console.hetzner.cloud/billing
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Troubleshooting
|
|
|
|
|
|
|
|
|
|
### Issue: Web Servers Unreachable
|
|
|
|
|
|
|
|
|
|
**Diagnosis**:
|
|
|
|
|
```bash
|
|
|
|
|
doctl compute droplet list
|
|
|
|
|
doctl compute firewall list-rules firewall-id
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Solution**:
|
|
|
|
|
- Check firewall allows ports 80, 443
|
|
|
|
|
- Verify droplets have public IPs
|
|
|
|
|
- Check web server application status
|
|
|
|
|
|
|
|
|
|
### Issue: Database Connection Failure
|
|
|
|
|
|
|
|
|
|
**Diagnosis**:
|
|
|
|
|
```bash
|
|
|
|
|
aws rds describe-db-instances
|
|
|
|
|
aws security-group describe-security-groups
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Solution**:
|
|
|
|
|
- Verify RDS security group allows port 5432 from web servers
|
|
|
|
|
- Check RDS status is "available"
|
|
|
|
|
- Verify connection string in application
|
|
|
|
|
|
|
|
|
|
### Issue: Backup Volume Not Mounted
|
|
|
|
|
|
|
|
|
|
**Diagnosis**:
|
|
|
|
|
```bash
|
|
|
|
|
hcloud volume list
|
|
|
|
|
ssh hetzner-volume
|
|
|
|
|
lsblk
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Solution**:
|
|
|
|
|
```bash
|
|
|
|
|
sudo mkfs.ext4 /dev/sdb
|
|
|
|
|
sudo mount /dev/sdb /mnt/backups
|
|
|
|
|
echo '/dev/sdb /mnt/backups ext4 defaults,nofail 0 0' | sudo tee -a /etc/fstab
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Cleanup
|
|
|
|
|
|
|
|
|
|
To destroy all resources:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# This will delete everything - use carefully
|
|
|
|
|
provisioning workspace destroy --config config.toml
|
|
|
|
|
|
|
|
|
|
# Or manually
|
|
|
|
|
doctl compute droplet delete web-server-1 web-server-2 web-server-3
|
|
|
|
|
doctl compute load-balancer delete web-lb
|
|
|
|
|
aws rds delete-db-instance --db-instance-identifier webapp-db --skip-final-snapshot
|
|
|
|
|
hcloud volume delete webapp-backups
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Next Steps
|
|
|
|
|
|
|
|
|
|
1. **SSL/TLS**: Update certificate and enable HTTPS
|
|
|
|
|
2. **Auto-scaling**: Add DigitalOcean autoscaling based on load
|
|
|
|
|
3. **Multi-region**: Add additional AWS RDS read replicas in other regions
|
|
|
|
|
4. **Disaster Recovery**: Test failover procedures
|
|
|
|
|
5. **Cost Optimization**: Review and optimize resource sizes
|
|
|
|
|
|
|
|
|
|
## Support
|
|
|
|
|
|
|
|
|
|
For issues or questions:
|
|
|
|
|
|
|
|
|
|
- Review the multi-provider deployment guide
|
|
|
|
|
- Check provider-specific documentation
|
|
|
|
|
- Review workspace logs with debug flag: ./deploy.nu --debug
|
|
|
|
|
|
|
|
|
|
## Files
|
|
|
|
|
|
|
|
|
|
- `workspace.ncl`: Infrastructure definition (Nickel)
|
|
|
|
|
- `config.toml`: Provider credentials and settings
|
|
|
|
|
- `deploy.nu`: Deployment automation script (Nushell)
|
|
|
|
|
- `README.md`: This file
|