This document describes the environment variables required for file storage functionality using Cloudflare R2.
Required Variables
S3_BUCKET
- Description: Name of your Cloudflare R2 bucket
- Example:
tendsocial - Required: Yes
- Used by: S3 library for all upload/download operations
S3_REGION
- Description: AWS region (use
autofor Cloudflare R2) - Example:
auto - Required: Yes
- Used by: S3 client initialization
S3_ENDPOINT
- Description: Cloudflare R2 endpoint URL
- Example:
https://370e3c7ed6e50972fea1a19da4eb21c6.r2.cloudflarestorage.com - Required: Yes (for R2)
- Used by: S3 client to connect to R2 instead of AWS S3
- How to find: Available in your R2 bucket settings under "Bucket Details"
AWS_ACCESS_KEY_ID
- Description: R2 access key ID
- Example:
ad056f0bb0e13dbd9683a87936492083 - Required: Yes
- Used by: S3 client authentication
- How to get: Create in Cloudflare Dashboard → R2 → Manage R2 API Tokens
AWS_SECRET_ACCESS_KEY
- Description: R2 secret access key
- Example:
4486318247cbd52ef5b1a2b9ebef8d0c011940361a5988881520adc459680db0 - Required: Yes
- Used by: S3 client authentication
- Security: Store as a secret in production (Google Cloud Secret Manager)
Private Media Delivery (Story 6.10.1)
⚠️ IMPORTANT: Media delivery is PRIVATE-ONLY. Do NOT configure public CDN domains.
Forbidden Variables (Story 6.10.1)
These variables MUST NOT be set. The server will refuse to boot if any are present:
CDN_DOMAIN- Custom public delivery domainCDN_BASE_URL- Public asset base URLR2_PUBLIC_DOMAIN- Public R2 domainR2_PUBLIC_BUCKET- Public bucket flagR2_PUBLIC_BUCKET_URL- Public bucket URLS3_PUBLIC_URL- Public S3 URLPUBLIC_BUCKET- Public bucket flagR2_DEV_URL- Direct r2.dev bindingASSET_BASE_URL- Legacy public-delivery bindingMEDIA_CDN_URL- Legacy public-delivery binding
Media Proxy Configuration
For asset delivery to external platforms and browsers:
MEDIA_PROXY_BASE_URL: Base URL of the private media proxy (e.g.,https://media-proxy.tendsocial.com)MEDIA_PROXY_SECRET: Shared secret for HMAC validation of proxy requests- Description: Routes external asset requests through a verified-domain proxy that enforces authentication and lease-based access
- Required: Recommended for production
- Used by: Media delivery to social platforms and in-browser rendering
Environment File Examples
Development (.env.local)
bash
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/tendsocial
# Auth
JWT_SECRET=your-dev-jwt-secret
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_PUBLISHABLE_KEY=your-supabase-key
# AI
GOOGLE_GENAI_API_KEY=your-gemini-api-key
# Storage (Cloudflare R2) - PRIVATE ONLY
S3_BUCKET=tendsocial-dev
S3_REGION=auto
S3_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
AWS_ACCESS_KEY_ID=your-r2-access-key-id
AWS_SECRET_ACCESS_KEY=your-r2-secret-access-key
# Media Proxy (for private asset delivery)
MEDIA_PROXY_BASE_URL=https://media-proxy-dev.tendsocial.com
MEDIA_PROXY_SECRET=your-dev-media-proxy-secret
# Cloudflare API (optional, for deployment verification)
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
CLOUDFLARE_ACCOUNT_ID=your-account-idProduction (Google Cloud Run Secrets)
Set these as environment variables or secrets in your Cloud Run service:
bash
# Storage (Cloudflare R2) - PRIVATE ONLY
S3_BUCKET=tendsocial
S3_REGION=auto
S3_ENDPOINT=https://370e3c7ed6e50972fea1a19da4eb21c6.r2.cloudflarestorage.com
AWS_ACCESS_KEY_ID=<from-secret-manager>
AWS_SECRET_ACCESS_KEY=<from-secret-manager>
# Media Proxy (for private asset delivery)
MEDIA_PROXY_BASE_URL=https://media-proxy.tendsocial.com
MEDIA_PROXY_SECRET=<from-secret-manager>
# Cloudflare API (for deployment verification)
CLOUDFLARE_API_TOKEN=<from-secret-manager>
CLOUDFLARE_ACCOUNT_ID=your-account-idValidation & Deployment Verification
The application enforces private media delivery at startup:
Configuration Gate (
assertPrivateMediaDelivery):- Fails if any public-delivery env vars are set
- Fails if
S3_ENDPOINTorR2_ENDPOINT_URLcontains.r2.dev - Runs automatically when the server starts (see
apps/backend/src/index.ts)
Deployment Verification (optional, recommended):
- Run
npx tsx apps/backend/src/scripts/verify-deployment.ts - Checks config gate + live R2 bucket state (if Cloudflare API creds present)
- Verifies that r2.dev managed domain is disabled
- Verifies that custom domains are not active
- Exit code: 0 (success) or 1 (violation)
- Run
Example:
bash
cd apps/backend
npx tsx src/scripts/verify-deployment.tsSecurity Best Practices
Never commit credentials to Git
- Use
.envfiles (add to.gitignore) - Use environment variables in CI/CD
- Use secret managers in production
- Use
Rotate credentials regularly
- Create new R2 API tokens every 90 days
- Update in all environments
Use least privilege
- R2 API tokens should only have permissions for the specific bucket
- Avoid using account-wide admin tokens
Monitor usage
- Set up alerts for unusual upload patterns
- Monitor R2 storage and request metrics
Troubleshooting
"S3_BUCKET environment variable is not set"
- Ensure
S3_BUCKETis set in your environment - Check that the variable is loaded (e.g., via
dotenv)
"Access Denied" errors
- Verify
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYare correct - Check that the R2 API token has permissions for the bucket
- Ensure the token hasn't expired
Files upload but authenticated previews don't work
- Check
S3_ENDPOINTand private R2 credentials. - Confirm
CDN_DOMAIN,CDN_BASE_URL, public custom domains, andr2.devaccess are disabled. - Verify the asset API returns a fresh short-lived
viewUrl; do not test or expose direct R2 URLs.
CORS errors in browser
- Ensure CORS is configured in R2 bucket settings
- See CDN Setup Guide for CORS configuration