File Storage Setup
Configure file storage - S3, Supabase Storage, Cloudflare R2, or MinIO
TL;DR: Set
STORAGE_PROVIDER=s3orsupabasewith credentials. Files upload directly to storage via presigned URLs.
Straktur uses presigned URLs for file uploads - files go directly from browser to storage, bypassing your server.
Supported Providers
| Provider | Best For | Pricing |
|---|---|---|
| Supabase Storage | If using Supabase already | Free tier included |
| AWS S3 | Production, high volume | Pay per use |
| Cloudflare R2 | No egress fees | Pay per use |
| MinIO | Self-hosted, development | Free (self-hosted) |
Supabase Storage
Recommended if you're already using Supabase for database.
Setup
- In Supabase dashboard, go to Storage
- Create a new bucket (e.g.,
files) - Get your service key from Settings → API → service_role
STORAGE_PROVIDER=supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIs...
SUPABASE_STORAGE_BUCKET=filesUse the service_role key, not the anon key. The service key bypasses Row Level Security.
Supabase Storage handles CORS automatically. No additional configuration needed.
AWS S3
Best for production - highly reliable, global CDN available.
Setup
- Create bucket in AWS S3 Console
- Create IAM user with S3 access
- Generate access keys
- Configure CORS (required for browser uploads)
STORAGE_PROVIDER=s3
S3_BUCKET=my-app-files
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
S3_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYIAM Policy (Minimum Required)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-app-files/*"
}
]
}CORS Configuration
In S3 Console → Bucket → Permissions → CORS:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["PUT", "GET"],
"AllowedOrigins": ["http://localhost:3000", "https://yourdomain.com"],
"ExposeHeaders": ["ETag"]
}
]Cloudflare R2
Best for cost - S3-compatible with zero egress fees.
Setup
- Create bucket in Cloudflare Dashboard → R2
- Create API token with R2 permissions
- Configure CORS (required for browser uploads)
STORAGE_PROVIDER=s3
S3_BUCKET=my-bucket
S3_REGION=auto
S3_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-keyCORS Configuration
In Cloudflare Dashboard → R2 → Bucket → Settings → CORS Policy:
[
{
"AllowedOrigins": ["http://localhost:3000", "https://yourdomain.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]R2 uses region=auto. The endpoint URL contains your Cloudflare account ID.
MinIO
Best for development - S3-compatible, runs locally.
Docker Setup
Included in docker-compose.yml. Just run:
docker-compose upSTORAGE_PROVIDER=s3
S3_BUCKET=straktur-dev
S3_REGION=us-east-1
S3_ENDPOINT=http://minio:9000
S3_PUBLIC_ENDPOINT=http://localhost:9000
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin- S3 API: http://localhost:9000
- Web Console: http://localhost:9001 (minioadmin/minioadmin)
S3_ENDPOINT is for server-side (container network).
S3_PUBLIC_ENDPOINT is for browser uploads (localhost).
CORS Configuration
CORS is pre-configured in docker-compose.yml. For manual setup via MinIO Console or CLI:
mc alias set myminio http://localhost:9000 minioadmin minioadmin
mc cors set myminio/straktur-dev --config cors.json{
"CORSRules": [
{
"AllowedOrigins": ["http://localhost:3000"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"]
}
]
}Environment Variables
| Variable | Required | Description |
|---|---|---|
STORAGE_PROVIDER | Yes | s3 or supabase |
STORAGE_INTENT_SECRET | Recommended | Upload token secret (min 32 chars) |
For S3/R2/MinIO
| Variable | Required | Description |
|---|---|---|
S3_BUCKET | Yes | Bucket name |
S3_REGION | Yes | AWS region or auto for R2 |
S3_ACCESS_KEY_ID | Yes | Access key |
S3_SECRET_ACCESS_KEY | Yes | Secret key |
S3_ENDPOINT | For R2/MinIO | Custom endpoint URL |
S3_PUBLIC_ENDPOINT | For Docker | Browser-accessible endpoint |
For Supabase
| Variable | Required | Description |
|---|---|---|
SUPABASE_URL | Yes | Project URL |
SUPABASE_SERVICE_KEY | Yes | Service role key |
SUPABASE_STORAGE_BUCKET | Yes | Bucket name |
How Uploads Work
┌─────────┐ 1. Request URL ┌─────────┐
│ Browser │ ──────────────────────► │ Server │
└─────────┘ └─────────┘
│ │
│ 2. Generate presigned URL
│ │
│ 3. Presigned URL + Token │
│ ◄──────────────────────────────── │
│ │
│ 4. Upload directly │
│ ─────────────────────────────► ┌─────────┐
│ │ Storage │
│ 5. Confirm upload └─────────┘
│ ──────────────────────────────► │
│ │
│ 6. File saved to DB │
│ ◄──────────────────────────────── │Benefits:
- No file size limits from serverless (bypasses 4.5MB Lambda limit)
- Faster uploads (direct to storage)
- Less server load
File Categories & Limits
| Category | Max Size | Allowed Types |
|---|---|---|
PICTURES | 10 MB | JPEG, PNG, WebP, GIF, SVG |
AVATARS | 5 MB | JPEG, PNG, WebP |
DOCUMENTS | 25 MB | PDF, DOC, DOCX, XLS, XLSX |
THUMBNAILS | 10 MB | JPEG, PNG, WebP, GIF, SVG |