File size: 4,275 Bytes
7c6ffa6 6515ef9 7c6ffa6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | # Cloud Storage Setup (R2 / S3-compatible)
DocDoe serves generated student audio and video through owner-authenticated API
routes. Keep any backing object store private; public bucket URLs are not an
acceptable delivery mechanism for student-generated artifacts.
## When to switch
| Env | STORAGE_PROVIDER | Notes |
|---|---|---|
| Local dev | `local` (default) | Files served from `/uploads`, `/generated/audio`, `/generated/videos` |
| Production (single-instance) | `r2` or `s3` | Required for stable public video URLs |
| Production (multi-instance) | `r2` or `s3` | Required β local volumes don't survive redeploys |
## Cloudflare R2 (recommended)
R2 is S3-compatible, zero egress fees, free tier up to 10GB.
### 1. Create bucket
1. Cloudflare Dashboard β R2 β Create bucket
2. Bucket name: `docdoe-media`
3. Region: auto
### 2. Enable public access
Option A β R2.dev subdomain (fastest):
- Bucket β Settings β "Public access" β Allow access via R2.dev subdomain
- Public URL prefix: `https://pub-<hash>.r2.dev`
Option B β Custom domain (recommended for production):
- Bucket β Settings β "Public access" β Connect custom domain
- Domain: `media.docdoe.ai`
- Cloudflare auto-creates CNAME
### 3. Create API token
1. Cloudflare Dashboard β R2 β "Manage R2 API Tokens"
2. Create token β "Object Read & Write" permission
3. Apply to bucket: `docdoe-media`
4. Copy Access Key ID + Secret Access Key (shown once)
### 4. Set env vars
```bash
STORAGE_PROVIDER=r2
STORAGE_BUCKET=docdoe-media
STORAGE_ENDPOINT_URL=https://<your-account-id>.r2.cloudflarestorage.com
STORAGE_ACCESS_KEY_ID=<R2 access key>
STORAGE_SECRET_ACCESS_KEY=<R2 secret>
STORAGE_PUBLIC_BASE_URL=https://media.docdoe.ai # or https://pub-<hash>.r2.dev
```
`STORAGE_REGION` not needed for R2.
### 5. CORS on bucket
R2 β Bucket β Settings β CORS Policy. Add:
```json
[
{
"AllowedOrigins": ["https://docdoe.ai", "https://www.docdoe.ai"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3600
}
]
```
For dev/preview, add Vercel preview origins or `https://*.vercel.app`.
## AWS S3 (alternative)
Same env vars, set `STORAGE_PROVIDER=s3` and `STORAGE_REGION=us-east-1` (or region of bucket). Endpoint URL not needed β boto3 derives it. Public URLs use `https://<bucket>.s3.<region>.amazonaws.com/<key>` unless `STORAGE_PUBLIC_BASE_URL` overrides.
### S3 bucket policy (public read)
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::docdoe-media/*"
}
]
}
```
Plus enable "Block all public access" β uncheck for this bucket.
## Verification
After deploy:
```bash
# Backend health
curl -s https://api.docdoe.ai/health
# Generate a small test video via /video-generator
# Then list video jobs:
curl -s -H "Authorization: Bearer <jwt>" https://api.docdoe.ai/video/render-jobs
# Final video URL should look like:
# https://media.docdoe.ai/generated-videos/<job-id>/output.mp4
# NOT like:
# http://localhost:8000/generated/videos/<job-id>/output.mp4
```
If you see localhost URLs in `public_url` field, `STORAGE_PROVIDER` is still `local`. Fix env and redeploy.
## Object key layout
| Key prefix | Content |
|---|---|
| `generated-audio/<job-id>/scene-XXX.wav` | TTS per-scene audio files |
| `generated-videos/<job-id>/output.mp4` | Final rendered MP4 |
| `uploads/<user-id>/<doc-id>.<ext>` | Original student uploads |
Local provider rewrites keys into `/generated/audio/...`, `/generated/videos/...`, `/uploads/...` paths served by FastAPI `StaticFiles`. R2/S3 provider serves directly from `STORAGE_PUBLIC_BASE_URL`.
## Failure modes
| Symptom | Cause | Fix |
|---|---|---|
| Video plays in dev, 404 in production | `STORAGE_PROVIDER=local` in prod | Switch to `r2`/`s3` |
| `boto3 is not installed` error | Image built without `pip install boto3` | Rebuild Dockerfile.backend (boto3 in requirements.txt) |
| Cloud upload fails | Missing access key, secret, or bucket var | Check `/health/deep` JSON for storage status |
| Video plays but no audio | Render container missing ffmpeg | Use Dockerfile.backend (includes ffmpeg) |
|