Day 02 Day 2

Day 2

Day 2

~1 hour Intermediate Hands-on Precision AI Academy

Today's Objective

Use S3 to host static websites, store application assets, and manage file uploads with presigned URLs and bucket policies.

S3: Simple Storage Service

S3 stores objects (files) in buckets. It's the foundation of most AWS architectures: static website hosting, file uploads, build artifacts, logs, backup storage, and AI dataset storage. The pricing is minimal and it scales infinitely.

Create a Bucket and Host a Static Site

terminal_-_create_bucket.txt
TERMINAL — CREATE BUCKET
# Bucket names must be globally unique
aws s3 mb s3://my-app-static-site-2026

# Enable static website hosting
aws s3 website s3://my-app-static-site-2026 \
  --index-document index.html \
  --error-document error.html

# Upload files
aws s3 sync ./dist s3://my-app-static-site-2026

# Make files publicly readable
aws s3api put-bucket-policy \
  --bucket my-app-static-site-2026 \
  --policy file://bucket-policy.json
bucket-policy.json.txt
BUCKET-POLICY.JSON
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "PublicReadGetObject",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-app-static-site-2026/*"
  }]
}

Your site will be available at http://my-app-static-site-2026.s3-website-us-east-1.amazonaws.com. In Day 5 we'll put CloudFront + HTTPS in front of it.

File Uploads with Presigned URLs

For user file uploads, never expose your AWS credentials to the browser. Instead, generate a presigned URL on the server — a time-limited URL that allows a specific S3 operation without needing credentials.

node.js_-_generate_presigned_upload_url.txt
NODE.JS — GENERATE PRESIGNED UPLOAD URL
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({ region: 'us-east-1' });

export async function getUploadUrl(filename, contentType) {
  const key = `uploads/${Date.now()}-${filename}`;
  const command = new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,
    ContentType: contentType,
  });
  const url = await getSignedUrl(s3, command, { expiresIn: 300 }); // 5 min
  return { url, key };
}

// Express route
app.post('/api/upload-url', async (req, res) => {
  const { filename, contentType } = req.body;
  const { url, key } = await getUploadUrl(filename, contentType);
  res.json({ url, key });
});
Frontend upload pattern: Call your API to get the presigned URL, then PUT the file directly to S3 from the browser. Your server never handles the file bytes — it just authorizes the upload.

S3 Bucket Policies vs IAM Policies

Bucket policies attach to the bucket and apply to anyone accessing it (including anonymous users if you use "Principal": "*"). IAM policies attach to users and roles. For public read, use a bucket policy. For your app's write access, use an IAM role.

Day 2 Exercise
  1. Create an S3 bucket with a unique name
  2. Enable static website hosting on the bucket
  3. Create a simple index.html and upload it with aws s3 sync
  4. Apply the public read bucket policy
  5. Visit the S3 website URL and confirm it loads

What's Next

The foundations from today carry directly into Day 3. In the next session the focus shifts to Day 3 — building directly on everything covered here.

Supporting Videos & Reading

Go deeper with these external references.

Day 2 Checkpoint

Before moving on, verify you can answer these without looking:

Live Bootcamp

Learn this in person — 2 days, 5 cities

Thu–Fri sessions in Denver, Los Angeles, New York, Chicago, and Dallas. $1,490 per seat. June–October 2026.

Reserve Your Seat →
Continue To Day 3
Day 3: AWS App Runner — Deploy Containerized Apps in Minutes