AWS Setup & IAM Configuration
Phase 1: Setup AWS Infrastructure
Overview
Configure AWS S3 bucket, IAM policies, and CORS for the storage service. Foundation for all subsequent phases.
Requirements
Functional:
- β’S3 bucket with proper folder structure
- β’IAM user/role with minimal permissions
- β’CORS configured for frontend uploads
Non-functional:
- β’Region: ap-southeast-1 (Singapore) for Vietnam users
- β’Encryption: SSE-S3 (server-side)
- β’Versioning: Disabled initially (cost saving)
Architecture
pickleballdot-storage/
βββ profiles/ # User avatars
β βββ {userId}/avatar.{ext}
βββ courts/ # Court images
β βββ {courtId}/{imageId}.{ext}
βββ clubs/ # Club media
β βββ {clubId}/
β βββ avatar.{ext}
β βββ cover.{ext}
βββ posts/ # User/club posts
β βββ {postId}/{imageId}.{ext}
βββ tournaments/ # Tournament banners
β βββ {tournamentId}/banner.{ext}
βββ messages/ # Message attachments
β βββ {conversationId}/{messageId}.{ext}
βββ temp/ # Presigned upload staging
βββ {uploadId}.{ext}
Implementation Steps
1. Create S3 Bucket
aws s3api create-bucket \
--bucket pickleballdot-storage \
--region ap-southeast-1 \
--create-bucket-configuration LocationConstraint=ap-southeast-1
2. Enable Server-Side Encryption
aws s3api put-bucket-encryption \
--bucket pickleballdot-storage \
--server-side-encryption-configuration '{
"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]
}'
3. Configure CORS
Create cors.json:
{
"CORSRules": [
{
"AllowedOrigins": ["http://localhost:3000", "https://admin.pickleballdot.com"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
}
aws s3api put-bucket-cors --bucket pickleballdot-storage --cors-configuration file://cors.json
4. Create IAM Policy
Create s3-storage-policy.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::pickleballdot-storage",
"arn:aws:s3:::pickleballdot-storage/*"
]
}
]
}
5. Create IAM User for Backend
aws iam create-user --user-name pickleballdot-storage-user
aws iam create-policy --policy-name PickleballDotStoragePolicy --policy-document file://s3-storage-policy.json
aws iam attach-user-policy --user-name pickleballdot-storage-user --policy-arn arn:aws:iam::ACCOUNT_ID:policy/PickleballDotStoragePolicy
aws iam create-access-key --user-name pickleballdot-storage-user
6. Environment Variables
Add to .env:
# AWS S3
AWS_REGION=ap-southeast-1
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_S3_BUCKET=pickleballdot-storage
# CDN-ready: change this to CloudFront URL when added
STORAGE_PUBLIC_URL=https://pickleballdot-storage.s3.ap-southeast-1.amazonaws.com
7. Bucket Policy for Public Read (MVP)
Since no CDN yet, allow public read for uploaded images:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::pickleballdot-storage/*",
"Condition": {
"StringNotLike": {
"s3:prefix": "temp/*"
}
}
}
]
}
aws s3api put-bucket-policy --bucket pickleballdot-storage --policy file://bucket-policy.json
Note: When CDN added, revert to private bucket + CloudFront OAI.
8. Public Access Settings (MVP)
For MVP with public bucket policy, allow bucket policy (but block ACLs):
aws s3api put-public-access-block \
--bucket pickleballdot-storage \
--public-access-block-configuration '{
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": false,
"RestrictPublicBuckets": false
}'
Note: When CDN added, set all to true and use CloudFront for access.
Related Code Files
- β’Create:
infrastructure/s3/cors.json - β’Create:
infrastructure/s3/s3-storage-policy.json - β’Modify:
.env.example
Success Criteria
- β’ S3 bucket created in ap-southeast-1
- β’ CORS configured for local + production origins
- β’ IAM user created with scoped permissions
- β’ Public access blocked
- β’ Environment variables documented
Risk Assessment
| Risk | Mitigation |
|---|---|
| Credentials leaked | Use IAM roles in production (ECS/Lambda), rotate keys |
| CORS misconfiguration | Test from browser, verify headers |
| Cost overrun | Set billing alerts, lifecycle policies later |