DigitalOcean
intermediate
35-45 minutes
Advanced Features
Master Spaces object storage, monitoring, CI/CD, Functions, and production best practices on DigitalOcean.
What you'll accomplish
Production-ready DigitalOcean deployment with storage, monitoring, and automation
Prerequisites
- Application deployed
- Basic understanding of DigitalOcean services
Spaces Object Storage
S3-compatible object storage for files, backups, and static assets.
Create a Space
bash# Create Space doctl compute spaces create myspace --region nyc3 # List Spaces doctl compute spaces list
Upload Files
Using s3cmd:
bash# Install s3cmd pip install s3cmd # Configure s3cmd --configure # Enter: # Access Key: (from DigitalOcean API) # Secret Key: (from DigitalOcean API) # S3 Endpoint: nyc3.digitaloceanspaces.com # DNS-style: %(bucket)s.nyc3.digitaloceanspaces.com # Upload file s3cmd put myfile.txt s3://myspace/ # Upload directory s3cmd sync ./uploads/ s3://myspace/uploads/ # List files s3cmd ls s3://myspace/
Using AWS CLI:
bash# Configure for DO Spaces aws configure --profile digitalocean # Region: nyc3 # Output: json # Set endpoint export AWS_ENDPOINT_URL=https://nyc3.digitaloceanspaces.com # Upload aws s3 cp myfile.txt s3://myspace/ --profile digitalocean # Sync directory aws s3 sync ./uploads/ s3://myspace/uploads/ --profile digitalocean
Access from Node.js
javascriptconst { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3'); const s3 = new S3Client({ endpoint: 'https://nyc3.digitaloceanspaces.com', region: 'nyc3', credentials: { accessKeyId: process.env.SPACES_KEY, secretAccessKey: process.env.SPACES_SECRET, }, }); // Upload file async function uploadFile(key, body) { await s3.send(new PutObjectCommand({ Bucket: 'myspace', Key: key, Body: body, ACL: 'public-read', })); return `https://myspace.nyc3.digitaloceanspaces.com/${key}`; }
CDN Integration
Enable CDN for faster delivery:
- Go to Spaces → Your Space → Settings
- Enable CDN
- Use CDN URL:
https://myspace.nyc3.cdn.digitaloceanspaces.com/file.jpg
CORS Configuration
json[ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "PUT", "POST", "DELETE"], "AllowedOrigins": ["https://example.com"], "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3600 } ]
Monitoring and Alerting
Built-in Monitoring
All resources report metrics automatically:
- CPU usage
- Memory usage
- Disk I/O
- Network bandwidth
Enable Monitoring Agent
For detailed metrics:
bash# Install agent on Droplet curl -sSL https://repos.insights.digitalocean.com/install.sh | sudo bash
Create Alerts
bash# Via Control Panel: # Monitoring → Create Alert Policy
Alert types:
- CPU > threshold
- Memory > threshold
- Disk usage > threshold
- Bandwidth > threshold
Uptime Checks
Monitor endpoints:
- Monitoring → Uptime → Create Check
- Configure:
- Target URL
- Check interval (1, 2, 5, 10 min)
- Regions to check from
- Alert threshold
Functions (Serverless)
Event-driven serverless functions.
Install Serverless CLI
bash# Install serverless tools doctl serverless install
Create Function
bash# Initialize project doctl serverless init myproject # Structure: # myproject/ # ├── packages/ # │ └── default/ # │ └── hello/ # │ └── hello.js # └── project.yml
Function Code
javascript// packages/default/hello/hello.js function main(args) { return { body: JSON.stringify({ message: `Hello, ${args.name || 'World'}!`, }), headers: { 'Content-Type': 'application/json' }, }; } exports.main = main;
Deploy Functions
bash# Connect namespace doctl serverless connect # Deploy doctl serverless deploy # Invoke doctl serverless functions invoke default/hello -p name:John
Web Function
yaml# project.yml packages: - name: api functions: - name: users runtime: nodejs:18 web: true parameters: DATABASE_URL: ${DATABASE_URL}
CI/CD with GitHub Actions
Deploy App Platform
yaml# .github/workflows/deploy.yml name: Deploy to DigitalOcean on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install doctl uses: digitalocean/action-doctl@v2 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: Deploy App run: doctl apps create-deployment ${{ secrets.APP_ID }}
Deploy to Droplet
yamlname: Deploy to Droplet on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Deploy via SSH uses: appleboy/[email protected] with: host: ${{ secrets.DROPLET_IP }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /var/www/myapp git pull origin main npm ci --production npm run build sudo systemctl restart myapp
Build and Push Container
yamlname: Build and Deploy Container on: push: branches: [main] env: REGISTRY: registry.digitalocean.com IMAGE_NAME: myregistry/myapp jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install doctl uses: digitalocean/action-doctl@v2 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: Login to Registry run: doctl registry login - name: Build and push run: | docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} . docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} - name: Update App Platform run: | doctl apps update ${{ secrets.APP_ID }} \ --spec <(yq '.services[0].image.tag = "${{ github.sha }}"' .do/app.yaml)
Container Registry
Private Docker registry.
Create Registry
bash# Via Control Panel: # Container Registry → Create Registry
Login to Registry
bashdoctl registry login
Push Images
bash# Tag image docker tag myapp:latest registry.digitalocean.com/myregistry/myapp:latest # Push docker push registry.digitalocean.com/myregistry/myapp:latest
Use in App Platform
yamlservices: - name: web image: registry_type: DOCR registry: myregistry repository: myapp tag: latest
Kubernetes (DOKS)
Managed Kubernetes clusters.
Create Cluster
bashdoctl kubernetes cluster create mycluster \ --region nyc1 \ --version latest \ --node-pool "name=default;size=s-2vcpu-2gb;count=3"
Connect kubectl
bashdoctl kubernetes cluster kubeconfig save mycluster
Deploy Application
yaml# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: registry.digitalocean.com/myregistry/myapp:latest ports: - containerPort: 3000 env: - name: NODE_ENV value: production
Cost Management
View Usage
bash# Get balance doctl balance get # List resources doctl compute droplet list doctl apps list doctl databases list
Cost Breakdown
View in Control Panel:
- Settings → Billing → Monthly Usage
Cost Optimization Tips
- Right-size Droplets - Start small, scale up
- Use App Platform Basic - $5/mo for small apps
- Delete unused resources - Snapshots, IPs, volumes
- Use dev databases - Free in App Platform
- Reserved Droplets - Save with commitments
Destroy Unused Resources
bash# Delete Droplet doctl compute droplet delete DROPLET_ID --force # Delete database doctl databases delete DB_ID --force # Delete snapshots doctl compute snapshot delete SNAPSHOT_ID --force
Production Checklist
Security
- SSH keys only (no passwords)
- Cloud firewall configured
- VPC for internal traffic
- Two-factor authentication enabled
- API tokens scoped appropriately
Reliability
- Load balancer for HA
- Multiple Droplets/instances
- Database with standby nodes
- Automated backups enabled
- Health checks configured
Performance
- CDN for static assets
- Connection pooling for databases
- Appropriate instance sizes
- Monitoring enabled
Monitoring
- Monitoring agent installed
- Alert policies created
- Uptime checks configured
- Log aggregation set up
Deployment
- CI/CD pipeline configured
- Deployment tested
- Rollback strategy defined
Complete Production Setup
bash#!/bin/bash set -e PROJECT="myapp" REGION="nyc1" echo "Creating production environment..." # Create VPC VPC_ID=$(doctl vpcs create \ --name ${PROJECT}-vpc \ --region $REGION \ --ip-range 10.10.10.0/24 \ --output json | jq -r '.[0].id') # Create database DB_ID=$(doctl databases create ${PROJECT}-db \ --engine pg \ --version 16 \ --size db-s-1vcpu-2gb \ --region $REGION \ --num-nodes 2 \ --private-network-uuid $VPC_ID \ --output json | jq -r '.[0].id') echo "Waiting for database..." while [ "$(doctl databases get $DB_ID --format Status --no-header)" != "online" ]; do sleep 10 done # Get database URL DB_URL=$(doctl databases connection $DB_ID --format URI --no-header) # Create Space for uploads doctl compute spaces create ${PROJECT}-uploads --region nyc3 # Create Droplets for i in 1 2; do doctl compute droplet create ${PROJECT}-web-$i \ --image ubuntu-22-04-x64 \ --size s-2vcpu-2gb \ --region $REGION \ --vpc-uuid $VPC_ID \ --tag-names ${PROJECT}-web \ --enable-monitoring done sleep 60 # Create load balancer with SSL CERT_ID=$(doctl compute certificate create \ --name ${PROJECT}-cert \ --type lets_encrypt \ --dns-names example.com,www.example.com \ --output json | jq -r '.[0].id') LB_ID=$(doctl compute load-balancer create \ --name ${PROJECT}-lb \ --region $REGION \ --tag-name ${PROJECT}-web \ --forwarding-rules "entry_protocol:https,entry_port:443,target_protocol:http,target_port:3000,certificate_id:$CERT_ID" \ --forwarding-rules "entry_protocol:http,entry_port:80,target_protocol:http,target_port:3000" \ --redirect-http-to-https \ --health-check "protocol:http,port:3000,path:/health" \ --output json | jq -r '.[0].id') # Create firewall doctl compute firewall create \ --name ${PROJECT}-fw \ --inbound-rules "protocol:tcp,ports:22,address:YOUR_IP/32" \ --inbound-rules "protocol:tcp,ports:3000,load_balancer_uid:$LB_ID" \ --outbound-rules "protocol:tcp,ports:all,address:0.0.0.0/0,::/0" \ --tag-names ${PROJECT}-web # Restrict database access doctl databases firewalls append $DB_ID \ --rule tag:${PROJECT}-web echo "Production environment ready!" echo "Load Balancer IP: $(doctl compute load-balancer get $LB_ID --format IP --no-header)" echo "Database URL: $DB_URL"
Note
Checkpoint: Configure Spaces storage, set up monitoring alerts, implement CI/CD, and verify your production checklist is complete.