AWS S3

Complete AWS S3 setup guide for production file uploads in under 5 minutes. Includes bucket creation, IAM user configuration, CORS setup, and security best practices for scalable storage.

AWS S3 Setup

Get AWS S3 configured for production file uploads in under 5 minutes. This guide covers everything from bucket creation to security best practices.

Why AWS S3? The most trusted object storage service with 99.999999999% durability, global CDN integration, and predictable pricing. Perfect for applications that need reliable, scalable file storage.

What You'll Accomplish

By the end of this guide, you'll have:

  • ✅ A secure S3 bucket configured for web uploads
  • ✅ IAM user with minimal required permissions
  • ✅ CORS configuration for your domain
  • ✅ Environment variables ready for production
  • ✅ Cost optimization settings enabled

Create AWS Account & S3 Bucket

If you don't have an AWS account, sign up for free - you get 5GB of S3 storage free for 12 months.

  1. Open S3 Console: Go to S3 Console
  2. Create Bucket: Click "Create bucket"
  3. Configure Basic Settings:
Bucket name: your-app-uploads-prod
Region: us-east-1 (or closest to your users)

Bucket Naming: Use a unique, descriptive name. Bucket names are global across all AWS accounts and cannot be changed later.

  1. Block Public Access: Keep all "Block public access" settings enabled (this is secure - we'll use presigned URLs)
  2. Enable Versioning: Recommended for data protection
  3. Create Bucket: Click "Create bucket"

Configure CORS for Web Access

Comprehensive CORS Guide: For detailed CORS configuration, testing, and troubleshooting, see the CORS & ACL Configuration Guide.

Your web application needs permission to upload files directly to S3.

  1. Open Your Bucket: Click on your newly created bucket
  2. Go to Permissions Tab: Click "Permissions"
  3. Edit CORS Configuration: Scroll to "Cross-origin resource sharing (CORS)" and click "Edit"
  4. Add CORS Rules:
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
    "AllowedOrigins": [
      "http://localhost:3000",
      "http://localhost:3001",
      "https://localhost:3000"
    ],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
    "AllowedOrigins": [
      "https://yourdomain.com",
      "https://www.yourdomain.com",
      "https://staging.yourdomain.com"
    ],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 86400
  }
]
  1. Save Changes: Click "Save changes"

Security Note: Only add origins you trust. Wildcards (*) should never be used in production - they allow any website to upload to your bucket.

Create IAM User with Minimal Permissions

Create a dedicated user for your application with only the permissions it needs.

  1. Open IAM Console: Go to IAM Console
  2. Create User:
    • Click "Users" → "Create user"
    • Username: your-app-s3-user
    • Select "Programmatic access" only
  3. Create Custom Policy:
    • Click "Attach policies directly"
    • Click "Create policy"
    • Use JSON editor and paste:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::your-app-uploads-prod/*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::your-app-uploads-prod"
    }
  ]
}
  1. Name the Policy: YourApp-S3-Upload-Policy
  2. Attach to User: Go back to user creation and attach your new policy
  3. Create User: Complete the user creation

Replace Bucket Name: Make sure to replace your-app-uploads-prod with your actual bucket name in the policy JSON.

Get Access Keys

Your application needs these credentials to generate presigned URLs.

  1. Select Your User: In IAM Users, click on your newly created user
  2. Security Credentials Tab: Click "Security credentials"
  3. Create Access Key:
    • Click "Create access key"
    • Select "Application running outside AWS"
    • Click "Next"
  4. Copy Credentials:
    • Access Key ID: Copy this value
    • Secret Access Key: Copy this value (you'll only see it once!)

Security Alert: Never commit these keys to version control or share them publicly. Use environment variables or secure key management services.

Configure Environment Variables

Add your AWS credentials to your application.

# .env.local
AWS_ACCESS_KEY_ID=your_access_key_id
AWS_SECRET_ACCESS_KEY=your_secret_access_key
AWS_REGION=us-east-1
AWS_S3_BUCKET_NAME=your-app-uploads-prod

# Optional: Custom domain for public files (CDN, CloudFront, etc.)
S3_CUSTOM_DOMAIN=https://cdn.yourdomain.com

# Optional: Enable S3 debug logging
DEBUG=aws-sdk:\*
# Use your hosting platform's environment variable system
# Never store production keys in .env files

# Vercel:
# vercel env add AWS_ACCESS_KEY_ID
# vercel env add AWS_SECRET_ACCESS_KEY

# Netlify:
# Add in Site settings > Environment variables

# Railway:
# Add in Variables tab

AWS_ACCESS_KEY_ID=your_access_key_id
AWS_SECRET_ACCESS_KEY=your_secret_access_key
AWS_REGION=us-east-1
AWS_S3_BUCKET_NAME=your-app-uploads-prod

# Optional: Custom domain for public files (CDN, CloudFront, etc.)
S3_CUSTOM_DOMAIN=https://cdn.yourdomain.com

Configure Custom Domain (Optional)

For better performance and branding, you can use a custom domain for your files.

  1. Create CloudFront Distribution:

    • Go to CloudFront Console
    • Click "Create Distribution"
    • Origin Domain: Select your S3 bucket
    • Origin Access: Use "Origin access control settings"
    • Default Cache Behavior: Allow all HTTP methods
    • Alternate Domain Names: Add your custom domain (e.g., cdn.yourdomain.com)
    • SSL Certificate: Request or import your certificate
  2. Add DNS Record:

    # Add CNAME record in your DNS
    cdn.yourdomain.com -> your-cloudfront-distribution.cloudfront.net
  3. Update Environment Variables:

    # Add to your .env.local
    S3_CUSTOM_DOMAIN=https://cdn.yourdomain.com

Option 2: Public Bucket with Custom Domain

If your bucket is public (not recommended for production):

  1. Configure Bucket for Website Hosting:

    • Go to your S3 bucket → Properties
    • Enable "Static website hosting"
    • Set index document to index.html
    • Note the website endpoint
  2. Add DNS Record:

    # Add CNAME record in your DNS
    uploads.yourdomain.com -> your-bucket.s3-website-region.amazonaws.com
  3. Update Environment Variables:

    # Add to your .env.local
    S3_CUSTOM_DOMAIN=https://uploads.yourdomain.com

Security Note: Public buckets are not recommended for production. Use CloudFront with proper access controls instead.

Configure Your App

Set up pushduck with your AWS S3 configuration:

// lib/upload.ts
import { createUploadConfig } from "pushduck/server";

export const { s3, config } = createUploadConfig()
  .provider("aws", {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    region: process.env.AWS_REGION!,
    bucket: process.env.AWS_S3_BUCKET_NAME!,
    // Optional: Custom domain for public files
    customDomain: process.env.S3_CUSTOM_DOMAIN,
  })
  .defaults({
    maxFileSize: "10MB",
    acl: "private", // Use 'public-read' for public files
  })
  .build();

export { s3 };

Custom Domain: When customDomain is configured, public URLs will use your custom domain instead of the S3 URL. Internal operations (upload, delete) still use S3 endpoints.

Test Your Configuration

Verify everything works by testing an upload.

  1. Start Your App: Run your development server
  2. Test Upload: Try uploading a file using your upload component
  3. Check S3: Verify the file appears in your S3 bucket
  4. Check Access: Verify you can access the uploaded file via its URL

If something's not working:

  • ✅ Check CORS configuration matches your domain
  • ✅ Verify IAM policy has correct bucket name
  • ✅ Confirm environment variables are loaded
  • ✅ Check browser console for specific error messages

🎉 Congratulations!

Your AWS S3 bucket is now ready for production! Here's what you've accomplished:

  • Secure Storage: Files are stored in AWS's enterprise-grade infrastructure
  • Cost Efficient: Pay only for what you use, with free tier coverage
  • Globally Accessible: Files available worldwide with low latency
  • Scalable: Handles millions of files without configuration changes
  • Secure Access: Minimal IAM permissions and proper CORS setup

💰 Cost Optimization

Keep your AWS bills low with these optimization tips:

Storage Classes

# Standard: $0.023 per GB/month - for frequently accessed files
# Standard-IA: $0.0125 per GB/month - for infrequently accessed files
# Glacier: $0.004 per GB/month - for archival (retrieval takes hours)

Lifecycle Policies

Set up automatic transitions to save money:

  1. Go to Your Bucket → Management → Lifecycle rules
  2. Create Rule:
    • Transition to Standard-IA after 30 days
    • Transition to Glacier after 90 days
    • Delete incomplete multipart uploads after 1 day

Request Optimization

  • Use CloudFront: Cache files globally to reduce S3 requests
  • Batch Operations: Group multiple operations when possible
  • Monitor Usage: Set up billing alerts for unexpected costs

🔒 Security Best Practices

Access Control

// Example: User-specific upload paths
{
  "prefix": "users/${user.id}/*",
  "maxFileSize": "10MB",
      "types": ["image/jpeg", "image/png"]
}

Monitoring

  1. Enable CloudTrail: Track all S3 API calls
  2. Set Up Alerts: Monitor unusual access patterns
  3. Regular Audits: Review IAM permissions quarterly

Backup Strategy

# Cross-region replication for critical data
Source Bucket: us-east-1
Replica Bucket: us-west-2
Replication: Real-time

🚀 What's Next?

Now that AWS S3 is configured, explore these advanced features:

🖼️ Image Uploads

Handle image uploads with optimization and validation

Image Guide →

🔐 Authentication

Secure your uploads with proper authentication

Security Guide →

🚀 Production Deploy

Essential checklist for production deployment

Production Guide →

🛠️ API Reference

Complete API documentation and examples

API Docs →

💡 Pro Tips

Naming Convention: Use consistent bucket naming like {company}-{app}- {environment}-uploads for easy management across multiple projects.

Cost Alert: Set up AWS billing alerts at $5, $20, and $50 to avoid surprise charges during development.

Performance: Place your S3 bucket in the same region as your application server for fastest presigned URL generation.


Need help with AWS S3 setup? Join our Discord community or check out the troubleshooting guide for common issues.