Overview
AWS S3 is more than object storage — it doubles as a highly available, serverless static website host. Pair it with CloudFront (AWS's global CDN), ACM for free SSL certificates, and Route 53 for DNS, and you get a production-grade website with sub-second global load times at a few cents per month.
This guide covers the full stack used to host this portfolio: S3 for file storage, CloudFront for caching and HTTPS termination, ACM for certificate management, Route 53 for domain routing, and GitHub Actions for zero-touch deployments on every git push.
Prerequisites
Architecture
ACM certificates for CloudFront must be provisioned in us-east-1 regardless of where your S3 bucket is.
Tutorial steps
Create and configure your S3 bucket
Create an S3 bucket with the same name as your domain (e.g. yourdomain.com). Disable "Block all public access" and enable static website hosting under the bucket's Properties tab. Set the index document to index.html.
aws s3 mb s3://yourdomain.com --region eu-west-1
aws s3 website s3://yourdomain.com \
--index-document index.html \
--error-document index.html
Set the bucket policy for public read
Create a bucket policy that allows CloudFront (via OAC) to read objects. Avoid making the bucket fully public — instead use an Origin Access Control attached to your CloudFront distribution.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowCloudFrontOAC",
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::yourdomain.com/*"
}]
}
Request an SSL certificate via ACM
Open ACM in the us-east-1 (N. Virginia) region — this is a hard requirement for CloudFront. Request a public certificate for your domain and wildcard (*.yourdomain.com). Validate via DNS by adding the CNAME records ACM provides to Route 53.
Create a CloudFront distribution
In CloudFront, create a new distribution. Set the origin domain to your S3 bucket (use the S3 REST endpoint, not the website endpoint). Attach an Origin Access Control. Under Viewer Protocol Policy, choose "Redirect HTTP to HTTPS". Attach your ACM certificate and set your domain as an Alternate Domain Name (CNAME).
Configure Route 53
Create a hosted zone for your domain in Route 53. Add an A record (Alias) pointing to your CloudFront distribution. If using a subdomain (e.g. www), add a CNAME record pointing to the CloudFront domain name.
Type: A — Alias
Alias target: dXXXXXXXXXXX.cloudfront.net
Set up GitHub Actions for CI/CD
Add a workflow file to your repo. On every push to main, it syncs your files to S3 and invalidates the CloudFront cache so visitors always get the latest version. Store your AWS credentials as GitHub repository secrets.
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Sync to S3
run: aws s3 sync . s3://yourdomain.com --delete
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: eu-west-1
- name: Invalidate CloudFront cache
run: aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
--paths "/*"
Upload your site and verify
Push your code to GitHub. The Actions workflow will sync to S3 automatically. Wait a few minutes for DNS propagation, then visit your domain over HTTPS to verify everything is working.