Providers

DigitalOcean Spaces

Set up DigitalOcean Spaces with CDN for fast, affordable file uploads

Using DigitalOcean Spaces

Set up DigitalOcean Spaces for fast, affordable file uploads with built-in CDN.

Why Choose DigitalOcean Spaces?

  • 💰 Predictable Pricing: Simple pricing with generous free tier
  • 🌐 Built-in CDN: Free CDN with all Spaces for global performance
  • 🔗 S3 Compatible: Works seamlessly with S3 tools and libraries
  • 🚀 Easy Setup: Simple configuration with great developer experience

1. Create a Space

  1. Go to DigitalOcean Cloud Panel
  2. Click "Create a Space"
  3. Choose a datacenter region (closest to your users)
  4. Enter a unique Space name (e.g., my-app-uploads)
  5. Choose "Restrict File Listing" for security
  6. Enable "CDN" for global distribution
  7. Click "Create a Space"

DigitalOcean automatically creates a CDN endpoint:

  • Space URL: https://my-app-uploads.nyc3.digitaloceanspaces.com
  • CDN URL: https://my-app-uploads.nyc3.cdn.digitaloceanspaces.com

Custom Domain (Optional)

Set up your own domain for branded URLs:

  1. Go to "Settings" in your Space
  2. Click "Add Custom Domain"
  3. Enter your domain (e.g., uploads.yourdomain.com)
  4. Add a CNAME record in your DNS:
    # DNS Record:
    uploads.yourdomain.com -> my-app-uploads.nyc3.cdn.digitaloceanspaces.com

3. Generate API Keys

  1. Go to "API" in your DigitalOcean dashboard
  2. Click "Spaces access keys"
  3. Click "Generate New Key"
  4. Enter a name (e.g., "My App Uploads")
  5. Save your Access Key and Secret Key

4. Configure CORS

In your Space settings, add this CORS configuration:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
    "AllowedOrigins": ["http://localhost:3000", "https://yourdomain.com"],
    "ExposeHeaders": ["ETag", "Content-Length"],
    "MaxAgeSeconds": 3000
  }
]

5. Configure Your App

Add to your .env.local:

# DigitalOcean Spaces Configuration
AWS_ACCESS_KEY_ID=your_spaces_access_key
AWS_SECRET_ACCESS_KEY=your_spaces_secret_key
AWS_ENDPOINT_URL=https://nyc3.digitaloceanspaces.com
AWS_REGION=nyc3
S3_BUCKET_NAME=your-space-name

# CDN Configuration (recommended)
DIGITALOCEAN_CDN_ENDPOINT=https://your-space-name.nyc3.cdn.digitaloceanspaces.com
# Or your custom domain:
# DIGITALOCEAN_CDN_ENDPOINT=https://uploads.yourdomain.com

6. Update Your Upload Configuration

// lib/upload.ts
import { createUploadConfig } from "pushduck/server";

export const { s3,  } = createUploadConfig()
  .provider("digitalOceanSpaces",{
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    region: process.env.AWS_REGION!, // e.g., 'nyc3', 'sfo3', 'ams3'
    bucket: process.env.S3_BUCKET_NAME!,
    // Use CDN for faster file serving
    cdnEndpoint: process.env.DIGITALOCEAN_CDN_ENDPOINT,
  })
  .defaults({
    maxFileSize: "50MB", // Spaces supports larger files
    acl: "public-read",
  })
  .build();

7. Test Your Setup

npx @pushduck/cli@latest test --provider digitalocean

This will verify your Spaces connection and upload a test file.

✅ You're Ready!

Your DigitalOcean Space is configured! Benefits:

  • Global CDN - Files served from 12+ locations worldwide
  • Affordable pricing - $5/month for 250GB + 1TB transfer
  • High performance - Built-in CDN acceleration

🌐 CDN Benefits

Automatic Global Distribution

Your files are automatically cached worldwide:

// Files are served from the nearest CDN location
const imageUrl = file.url; // Automatically CDN-accelerated

// CDN locations include:
// - North America: NYC, SF, Toronto
// - Europe: Amsterdam, London, Frankfurt
// - Asia: Singapore, Bangalore

Cache Control

Optimize caching for different file types:

// Configure caching per upload type
const imageUpload = s3
  .image()
  .max("10MB")
  .onUploadComplete(async ({ file, key }) => {
    // Set cache headers for images (long cache)
    await setObjectMetadata(key, {
      "Cache-Control": "public, max-age=31536000", // 1 year
      "Content-Type": file.type,
    });
  });

const documentUpload = s3
  .file()
  .types(["pdf", "docx"])
  .onUploadComplete(async ({ file, key }) => {
    // Shorter cache for documents
    await setObjectMetadata(key, {
      "Cache-Control": "public, max-age=86400", // 1 day
    });
  });

🔧 Advanced Configuration

Multiple Regions

Deploy across multiple regions for redundancy:

// Primary region (closest to users)
const primarySpaces = createUploadConfig().provider("digitalOceanSpaces",{
  region: "nyc3", // New York
  bucket: "my-app-uploads-us",
});

// Backup region
const backupSpaces = createUploadConfig().provider("digitalOceanSpaces",{
  region: "ams3", // Amsterdam
  bucket: "my-app-uploads-eu",
});

Lifecycle Policies

Automatically manage old files:

// Configure automatic cleanup
.hooks({
  onUploadComplete: async ({ file, metadata }) => {
    // Schedule deletion of temporary files
    if (metadata.category === 'temp') {
      await scheduleCleanup(file.key, { days: 7 });
    }
  }
})

💰 Pricing Breakdown

ResourceIncludedOverage
Storage250 GB$0.02/GB
Transfer1 TB$0.01/GB
CDNIncludedFree
RequestsUnlimitedFree

Total: $5/month for most small-medium apps

🔒 Security Features

Private Spaces

For sensitive files:

// Configure private access
export const { s3,  } = createUploadConfig()
  .provider("digitalOceanSpaces",{
    // ... config
    acl: "private", // Files not publicly accessible
  })
  .defaults({
    generatePresignedUrl: true, // Generate secure URLs
    urlExpirationHours: 1, // URLs expire after 1 hour
  })
  .build();

File Access Control

Control who can access what:

.middleware(async ({ req, file }) => {
  const user = await authenticate(req);

  // Only allow users to upload to their own folder
  const userPrefix = `users/${user.id}/`;

  return {
    userId: user.id,
    keyPrefix: userPrefix,
  };
})

📊 Monitoring & Analytics

Usage Monitoring

Track your Space usage:

// Monitor uploads in real-time
.hooks({
  onUploadStart: async ({ file }) => {
    await analytics.track("upload_started", {
      provider: "digitalocean-spaces",
      fileSize: file.size,
      fileType: file.type,
    });
  },
  onUploadComplete: async ({ file, metadata }) => {
    await analytics.track("upload_completed", {
      provider: "digitalocean-spaces",
      duration: metadata.uploadTime,
      cdnEnabled: true,
    });
  }
})

🆘 Common Issues

CORS errors? → Verify your domain is in AllowedOrigins and CORS is enabled
Slow uploads? → Check you're using the correct regional endpoint
CDN not working? → Verify CDN is enabled and using the cdn.digitaloceanspaces.com endpoint
Access denied? → Check your API keys have Spaces read/write permissions
File not found? → Ensure you're using the CDN endpoint for file access

🚀 Performance Tips

  1. Use CDN endpoints for all file access (not direct Space URLs)
  2. Choose closest region to your primary user base
  3. Enable gzip compression for text files
  4. Set proper cache headers for different file types
  5. Use progressive image formats (WebP, AVIF) when possible

Next: Upload Your First Image or try MinIO Setup