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

javascript
const { 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:

  1. Go to Spaces → Your Space → Settings
  2. Enable CDN
  3. 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:

  1. Monitoring → Uptime → Create Check
  2. 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

yaml
name: 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

yaml
name: 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

bash
doctl 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

yaml
services:
  - name: web
    image:
      registry_type: DOCR
      registry: myregistry
      repository: myapp
      tag: latest

Kubernetes (DOKS)

Managed Kubernetes clusters.

Create Cluster

bash
doctl kubernetes cluster create mycluster \
  --region nyc1 \
  --version latest \
  --node-pool "name=default;size=s-2vcpu-2gb;count=3"

Connect kubectl

bash
doctl 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

  1. Right-size Droplets - Start small, scale up
  2. Use App Platform Basic - $5/mo for small apps
  3. Delete unused resources - Snapshots, IPs, volumes
  4. Use dev databases - Free in App Platform
  5. 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.