Skip to content
Back to blog
Part 5 of 10 in series
Building a Production-Grade Blog

A curated collection of articles exploring this topic in depth.

4 min read

AWS, but Deliberately

Why using cloud primitives directly provides a better foundation for ownership and security than 'magic' hosting platforms.


In the world of static hosting, there is no shortage of platforms that promise a "zero-config" experience. They handle the storage, the CDN, and the TLS certificates with a single click or a git push. For many projects, this is the right choice.

However, when the goal is intentional engineering and long-term ownership, these platforms can become a form of "magic" that obscures the underlying system. For this blog, I chose to use AWS primitives: S3 for storage, CloudFront for delivery, Route 53 for DNS, and ACM for certificates.

This approach requires more effort, but it provides a degree of control and security that managed platforms often abstract away (which is also one of the key reasons to choose such a platform in the first place).

Primitives over platforms#

By using AWS primitives, I am building on the same building blocks that power some of the largest systems in the world. This provides three distinct advantages:

  1. Portability: I am not locked into a specific provider's proprietary deployment flow. If I need to move to another cloud or a different CDN, the architectural patterns remain the same.
  2. Transparency: There are no hidden "magic" features. I know exactly how my content is stored, how it is cached, and how it is secured because I defined those configurations myself.
  3. Cost and Scale: For a static site, S3 and CloudFront are inexpensive. More importantly, they scale automatically without requiring a change in architecture or a move to a "Pro" plan.

The storage layer: Private S3#

The configuration shown here reflects my own implementation, but the underlying pattern, private origins with explicit delivery paths, is the important part.

In many tutorials, S3 buckets for static websites are configured for “Static Website Hosting” and made public. For a production-grade system, this is usually a poor default: it often requires disabling Block Public Access, and S3 website endpoints do not support HTTPS.

The risk is not that public content is inherently wrong. It is that making the bucket public weakens the security boundary and makes accidental exposure easier over time. The safest baseline is to treat S3 as a private storage layer and expose content only through a controlled delivery path such as CloudFront.

In this architecture, the S3 bucket is entirely private. Public access is explicitly blocked at the bucket level.

resource "aws_s3_bucket_public_access_block" "site" {
  bucket = aws_s3_bucket.site.id
 
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

This ensures that the content is only accessible through the intended delivery path.

The delivery layer: CloudFront and OAC#

To get content from a private bucket to a user, we use CloudFront with Origin Access Control (OAC). OAC allows CloudFront to sign requests to S3 using its own service principal.

This creates a strict security boundary: S3 only trusts CloudFront, and CloudFront only serves what it is configured to serve. This setup prevents "S3 bucket crawling" and ensures that all traffic follows the caching and security rules defined at the edge.

Ownership of the edge#

Using CloudFront directly also allows for fine-grained control over the delivery layer. For example, I use a CloudFront Function to handle "pretty URLs" (rewriting /about to /about/index.html) and to manage redirects from the apex domain to the blog. subdomain.

Doing this at the CDN level, rather than in the application code, is more efficient and follows the principle of handling concerns at the appropriate layer of the stack.

Choosing your abstractions#

Building on AWS primitives is not about being a purist; it is about choosing where to place your trust. By managing the infrastructure myself, I am trading some initial convenience for a deep understanding of the system's security and delivery model.

For a personal project, this is a valuable practice ground. It is a way to ensure that when you build larger systems, you are doing so from a foundation of first-hand experience with the primitives that underpin the modern web.

In the next post, we will look at how we manage these primitives using Terraform, turning our infrastructure into executable documentation.