# DigitalOcean Spaces Setup (S3-Compatible Object Storage)

**Audience**: Junior DevOps Engineers, Infrastructure Team
**Time to Complete**: 15-20 minutes
**Prerequisites**: DigitalOcean account with billing enabled

---

## Overview

This guide sets up **DigitalOcean Spaces** - an S3-compatible object storage service for storing files, uploads, and media for your MaplePress backend.

**What You'll Build:**
- DigitalOcean Space (bucket) for file storage
- API keys (access key + secret key) for programmatic access
- Docker Swarm secrets for secure credential storage
- Configuration ready for backend integration

**Why DigitalOcean Spaces?**
- S3-compatible API (works with AWS SDK)
- Simple pricing: $5/mo for 250GB + 1TB transfer
- CDN included (speeds up file delivery globally)
- No egress fees within same region
- Integrated with your existing DigitalOcean infrastructure

---

## Table of Contents

1. [Create DigitalOcean Space](#step-1-create-digitalocean-space)
2. [Generate API Keys](#step-2-generate-api-keys)
3. [Create Docker Secrets](#step-3-create-docker-secrets)
4. [Verify Configuration](#step-4-verify-configuration)
5. [Test Access](#step-5-test-access)
6. [Troubleshooting](#troubleshooting)

---

## Step 1: Create DigitalOcean Space

### 1.1 Create Space via Dashboard

1. Log into DigitalOcean dashboard: https://cloud.digitalocean.com
2. Click **Manage** → **Spaces Object Storage** in left sidebar
3. Click **Create a Space**
4. Configure:
   - **Choose a datacenter region**: Select same region as your droplets (e.g., `NYC3` or `Toronto`)
   - **Enable CDN**: ✅ Yes (recommended - improves performance globally)
   - **Choose a unique name**: `maplepress` (must be globally unique)
   - **Select a project**: Your project (e.g., "MaplePress Production")
5. Click **Create a Space**

**Expected output:**
- Space created successfully
- You'll see the space URL: `https://maplepress.tor1.digitaloceanspaces.com`

### 1.2 Record Space Information

**Save these values** (you'll need them later):

```bash
# Space Name
SPACE_NAME=maplepress

# Endpoint (without https://)
SPACE_ENDPOINT=tor1.digitaloceanspaces.com

# Region code
SPACE_REGION=tor1

# Full URL (for reference)
SPACE_URL=https://maplepress.tor1.digitaloceanspaces.com
```

**Region codes for reference:**
- Toronto: `tor1.digitaloceanspaces.com`
- San Francisco 3: `sfo3.digitaloceanspaces.com`
- Singapore: `sgp1.digitaloceanspaces.com`
- Amsterdam: `ams3.digitaloceanspaces.com`
- Frankfurt: `fra1.digitaloceanspaces.com`

**✅ Checkpoint:** Space created and URL recorded

---

## Step 2: Generate API Keys

### 2.1 Create Spaces Access Keys

1. In DigitalOcean dashboard, go to **API** in left sidebar
2. Scroll down to **Spaces access keys** section
3. Click **Generate New Key**
4. Configure:
   - **Name**: `maplepress-backend-prod`
   - **Description**: "Backend service access to Spaces" (optional)
5. Click **Generate Key**

**⚠️ CRITICAL:** The secret key is **only shown once**! Copy it immediately.

### 2.2 Save Credentials Securely

You'll see:
- **Access Key**: `DO00ABC123XYZ...` (20 characters)
- **Secret Key**: `abc123def456...` (40 characters)

**SAVE BOTH IN YOUR PASSWORD MANAGER NOW!**

Example:
```
DigitalOcean Spaces - MaplePress Production
Access Key: DO00ABC123XYZ456
Secret Key: abc123def456ghi789jkl012mno345pqr678stu901
Endpoint: nyc3.digitaloceanspaces.com
Bucket: maplepress
```

### 2.3 Update Local .env File

**On your local machine:**

```bash
# Navigate to production infrastructure
cd ~/monorepo/cloud/infrastructure/production

# Edit .env file
vi .env

# Add these lines:
SPACES_ACCESS_KEY=DO00ABC123XYZ456
SPACES_SECRET_KEY=abc123def456ghi789jkl012mno345pqr678stu901
SPACES_ENDPOINT=tor1.digitaloceanspaces.com
SPACES_REGION=tor1
SPACES_BUCKET=maplepress
```

Save: `Esc`, `:wq`, `Enter`

**✅ Checkpoint:** API keys saved securely in password manager and `.env` file

---

## Step 3: Create Docker Secrets

**On manager node:**

```bash
# SSH to manager
ssh dockeradmin@<manager-public-ip>
```

### 3.1 Create Spaces Access Key Secret

```bash
# Create secret for access key
echo -n "DO00ABC123XYZ456" | docker secret create spaces_access_key -

# Verify
docker secret ls | grep spaces_access_key
# Should show: spaces_access_key   About a minute ago
```

**Important:** Replace `DO00ABC123XYZ456` with your actual access key!

### 3.2 Create Spaces Secret Key Secret

```bash
# Create secret for secret key
echo -n "abc123def456ghi789jkl012mno345pqr678stu901" | docker secret create spaces_secret_key -

# Verify
docker secret ls | grep spaces_secret_key
# Should show: spaces_secret_key   About a minute ago
```

**Important:** Replace with your actual secret key!

### 3.3 Verify All Secrets

```bash
# List all secrets
docker secret ls
```

**You should see:**

```
ID                          NAME                         CREATED
abc123...                   maplepress_jwt_secret        from 05_backend.md
abc124...                   maplepress_ip_encryption_key from 05_backend.md
def456...                   redis_password               from 03_redis.md
ghi789...                   meilisearch_master_key       from 04_meilisearch.md
jkl012...                   spaces_access_key            NEW!
mno345...                   spaces_secret_key            NEW!
```

**✅ Checkpoint:** All secrets created successfully

---

## Step 4: Verify Configuration

### 4.1 Test Space Access from Local Machine

**Install AWS CLI (if not already installed):**

```bash
# On your local machine (Mac)
brew install awscli

# Or on Linux:
sudo apt install awscli
```

**Configure AWS CLI for DigitalOcean Spaces:**

```bash
# Create AWS credentials file
mkdir -p ~/.aws
vi ~/.aws/credentials

# Add this profile:
[digitalocean]
aws_access_key_id = DO00ABC123XYZ456
aws_secret_access_key = abc123def456ghi789jkl012mno345pqr678stu901
```

Save: `Esc`, `:wq`, `Enter`

### 4.2 Test Listing Space Contents

```bash
# List contents of your space
aws s3 ls s3://maplepress \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean

# Should show empty (new space) or list existing files
```

### 4.3 Test File Upload

```bash
# Create test file
echo "Hello from MaplePress!" > test-file.txt

# Upload to space
aws s3 cp test-file.txt s3://maplepress/test-file.txt \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean \
  --acl public-read

# Should show: upload: ./test-file.txt to s3://maplepress/test-file.txt
```

### 4.4 Test File Download

```bash
# Download from space
aws s3 cp s3://maplepress/test-file.txt downloaded-test.txt \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean

# Verify content
cat downloaded-test.txt
# Should show: Hello from MaplePress!

# Clean up
rm test-file.txt downloaded-test.txt
```

### 4.5 Test Public URL Access

```bash
# Try accessing via browser or curl
curl https://maplepress.tor1.digitaloceanspaces.com/test-file.txt

# Should show: Hello from MaplePress!
```

**✅ Checkpoint:** Successfully uploaded, listed, downloaded, and accessed file

---

## Step 5: Test Access

### 5.1 Verify Endpoint Resolution

```bash
# Test DNS resolution
dig tor1.digitaloceanspaces.com +short

# Should return IP addresses (e.g., 192.81.xxx.xxx)
```

### 5.2 Test HTTPS Connection

```bash
# Test SSL/TLS connection
curl -I https://tor1.digitaloceanspaces.com

# Should return:
# HTTP/2 403 (Forbidden is OK - means endpoint is reachable)
```

### 5.3 Check Space Permissions

1. Go to DigitalOcean dashboard → Spaces
2. Click on your space (`maplepress`)
3. Click **Settings** tab
4. Check **File Listing**: Should be ❌ Restricted (recommended for security)
5. Individual files can be made public via ACL when uploading

**✅ Checkpoint:** Spaces endpoint is accessible and working

---

## Troubleshooting

### Problem: "Space name already exists"

**Symptom:** Can't create space with chosen name

**Cause:** Space names are globally unique across all DigitalOcean customers

**Solution:**

Try these naming patterns:
- `maplepress-<your-company>`
- `maplepress-<random-string>`
- `mp-prod-<date>` (e.g., `mp-prod-2025`)

Check availability by trying different names in the creation form.

### Problem: "Access Denied" When Testing

**Symptom:** AWS CLI returns `AccessDenied` error

**Causes and Solutions:**

1. **Wrong credentials:**
   ```bash
   # Verify credentials in ~/.aws/credentials match DigitalOcean dashboard
   cat ~/.aws/credentials
   ```

2. **Wrong endpoint:**
   ```bash
   # Make sure endpoint matches your space region
   # NYC3: nyc3.digitaloceanspaces.com
   # SFO3: sfo3.digitaloceanspaces.com
   ```

3. **Wrong bucket name:**
   ```bash
   # Verify bucket name matches space name exactly
   aws s3 ls --endpoint-url https://tor1.digitaloceanspaces.com --profile digitalocean
   # Should list your space
   ```

### Problem: "NoSuchBucket" Error

**Symptom:** AWS CLI says bucket doesn't exist

**Check:**

```bash
# List all spaces in your account
aws s3 ls --endpoint-url https://tor1.digitaloceanspaces.com --profile digitalocean

# Make sure your space appears in the list
```

**If space is missing:**
- Check you're in the correct DigitalOcean account
- Check space wasn't accidentally deleted
- Check endpoint URL matches space region

### Problem: Files Not Publicly Accessible

**Symptom:** Get 403 Forbidden when accessing file URL

**Cause:** File ACL is private (default)

**Solution:**

```bash
# Upload with public-read ACL
aws s3 cp file.txt s3://maplepress/file.txt \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean \
  --acl public-read

# Or make existing file public
aws s3api put-object-acl \
  --bucket maplepress \
  --key file.txt \
  --acl public-read \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean
```

**Note:** Your backend will control ACLs programmatically. Public access should only be granted to files that need to be publicly accessible (e.g., user-uploaded images for display).

### Problem: CDN Not Working

**Symptom:** Files load slowly or CDN URL doesn't work

**Check:**

1. Verify CDN is enabled:
   - DigitalOcean dashboard → Spaces → Your space → Settings
   - **CDN** should show: ✅ Enabled

2. Use CDN URL instead of direct URL:
   ```bash
   # Direct URL (slower):
   https://maplepress.tor1.digitaloceanspaces.com/file.txt

   # CDN URL (faster):
   https://maplepress.tor1.cdn.digitaloceanspaces.com/file.txt
   ```

3. Clear CDN cache if needed:
   - Spaces → Your space → Settings → CDN
   - Click **Purge Cache**

### Problem: High Storage Costs

**Symptom:** Unexpected charges for Spaces

**Check:**

```bash
# Calculate total space usage
aws s3 ls s3://maplepress --recursive --human-readable --summarize \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean

# Shows: Total Size: X.XX GB
```

**Pricing reference:**
- $5/mo includes 250GB storage + 1TB outbound transfer
- Additional storage: $0.02/GB per month
- Additional transfer: $0.01/GB

**Optimization tips:**
- Delete old/unused files regularly
- Use CDN to reduce direct space access
- Compress images before uploading
- Set up lifecycle policies to auto-delete old files

---

## Next Steps

✅ **You now have:**
- DigitalOcean Space created and configured
- API keys generated and secured
- Docker Swarm secrets created
- Verified access from local machine

**Next guide:**
- **05_backend.md** - Deploy MaplePress backend
- Backend will use these Spaces credentials automatically
- Files uploaded via backend API will be stored in your Space

**Space Configuration for Backend:**

The backend will use these environment variables (configured in 05_backend.md):

```yaml
environment:
  - AWS_ACCESS_KEY_FILE=/run/secrets/spaces_access_key
  - AWS_SECRET_KEY_FILE=/run/secrets/spaces_secret_key
  - AWS_ENDPOINT=https://tor1.digitaloceanspaces.com
  - AWS_REGION=tor1
  - AWS_BUCKET_NAME=maplepress
```

**Useful Commands:**

```bash
# List all files in space
aws s3 ls s3://maplepress --recursive \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean

# Get space size
aws s3 ls s3://maplepress --recursive --summarize \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean

# Delete test file
aws s3 rm s3://maplepress/test-file.txt \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean

# Sync local directory to space
aws s3 sync ./local-folder s3://maplepress/uploads/ \
  --endpoint-url https://tor1.digitaloceanspaces.com \
  --profile digitalocean
```

---

**Last Updated**: January 2025
**Maintained By**: Infrastructure Team

**Changelog:**
- January 2025: Initial DigitalOcean Spaces setup guide for MaplePress production deployment
