Static Website on AWS with CI/CD (S3 + CloudFront)

This project documents how I built, deployed and iterated on a static website on AWS using S3 for storage, CloudFront for content delivery, Route 53 for DNS and ACM for HTTPS. It also covers automated deployment, version control, SEO improvements, search indexing, key decisions, challenges and running costs.

Context

This project was built to create a fast, secure and low-maintenance portfolio site on AWS. It is used in production to serve this website.

Key constraints:

  • Keep the architecture simple and low cost
  • Serve the site securely over HTTPS
  • Maintain minimal operational overhead
  • Make updates easy to manage and recover

Architecture

Runtime Architecture

User requests are routed through Route 53 to a CloudFront distribution, which serves cached content from an S3 bucket. ACM provides the SSL certificate used for HTTPS delivery through CloudFront.

The site is built as static HTML and CSS, with updates managed locally and version-controlled in GitHub before deployment to AWS.

Deployment is automated through GitHub Actions. Changes pushed to the main branch trigger a workflow that syncs the site files to S3 and creates a CloudFront invalidation so updates are published automatically.

This setup keeps the architecture simple while using managed services for storage, delivery and DNS, reducing operational overhead compared with managing a server-based solution.

Automated Deployment Pipeline

This pipeline removes manual S3 uploads and creates a repeatable deployment workflow. GitHub Actions uses OIDC to assume a least-privilege AWS IAM role, then deploys updated site files and invalidates CloudFront so changes are reflected immediately on the live site.

Key Decisions

Use CloudFront in front of S3

Why: This improves performance through caching, enables HTTPS delivery, and allows the S3 origin to remain private instead of exposing a public bucket.

Trade-off: It adds another layer to configure compared with serving directly from S3.

Use Route 53 for DNS

Why: It integrates cleanly with AWS resources and makes domain routing straightforward.

Trade-off: It keeps DNS management inside AWS rather than using an external provider.

Use Git and GitHub for version control

Why: This provides a clear backup and change history for the site, making updates safer and easier to roll back if needed.

Trade-off: It adds an extra step to the workflow and requires discipline to keep changes committed consistently.

Automate deployment with GitHub Actions

Why: This removes manual S3 uploads and creates a repeatable deployment workflow from GitHub to AWS.

Trade-off: It requires additional setup across GitHub Actions, IAM and CloudFront permissions.

Use OIDC instead of long-lived AWS access keys

Why: OIDC allows GitHub Actions to assume an AWS role securely without storing static AWS credentials in GitHub.

Trade-off: It is more complex to configure than simple access keys, but provides a stronger security model.

Build SEO and indexing into the site structure

Why: Clear page titles, meta descriptions, canonical tags and internal linking help search engines understand the site and improve discoverability.

Trade-off: It adds content and markup decisions that need to be maintained alongside the technical build.

Challenges

S3 access and CloudFront permissions

Resolving 403 Access Denied errors caused by misaligned bucket policy and CloudFront Origin Access Control configuration.

HTTPS and custom domain setup

Configuring ACM, CloudFront and Route 53 in the correct order to enable certificate validation and domain routing.

Deployment and cache consistency

Making sure updated HTML, CSS and assets were actually reflected on the live site, including handling CloudFront invalidations and browser caching during testing.

Secure CI/CD permissions

Configuring GitHub Actions to deploy using OIDC, with an IAM role restricted to the repository, main branch, S3 bucket and CloudFront invalidation permissions required for the site.

Search visibility and crawl readiness

Improving page titles, descriptions, canonical tags and internal linking so the site was better structured for indexing and easier for search engines to interpret.

Cost

Estimated monthly infrastructure cost: ~$0.50 USD (excluding VAT, based on AWS Pricing Calculator and current usage)

  • Route 53 → ~$0.50 USD fixed monthly cost (hosted zone)
  • CloudFront → within free tier at current usage (~1 GB/month), scales with traffic
  • S3 → negligible storage and request costs (<50 MB)

Additional cost:

  • Domain registration → ~£15/year (excluding VAT, purchased via Route 53)

Outcome

The result is a production-ready static website hosted on AWS with secure HTTPS delivery, global content distribution and an automated deployment model. The project also established a GitHub-based backup workflow and a stronger SEO foundation for future growth.

  • The site is simple, fast and low maintenance
  • The project gave me hands-on experience connecting core AWS networking, delivery and DNS services
  • Deployment is now automated from GitHub to AWS using GitHub Actions
  • The pipeline uses OIDC rather than long-lived AWS access keys
  • The site now has a clearer structure for search engines, with indexing and discoverability considered as part of the build

Next Steps

Add a staging environment using a separate branch, S3 bucket, CloudFront distribution and staging subdomain to enable safe testing before production deployment.