Skip to content
Arman Zeinali

Architecture

How this site is built

A static site on AWS that I set up by hand in the AWS Console. This page explains each piece and why it is there.

Hosting architecture for armanzeinali.ca

Request path: what happens when someone visits

  1. Visitor

    Opens armanzeinali.ca

  2. Route 53

    DNS points the domain at CloudFront

  3. CloudFront

    HTTPS with an ACM certificate. A CloudFront Function maps clean URLs and redirects www. Adds security headers

  4. Origin Access Control

    Signs each request to the bucket

  5. S3 bucket

    Private. Only this CloudFront distribution can read it

Publish path today: how a change goes live

  1. Build

    Astro builds the static site on my computer

  2. Upload to S3

    New files uploaded to the private bucket in the AWS Console

  3. Invalidate

    A CloudFront invalidation clears cached copies

  4. Live

    Visitors get the new version within minutes

In progress: the same setup as infrastructure as code with AWS CDK, and automated deploys with GitHub Actions using OIDC instead of stored keys.

The short version

This is a static website. The HTML, CSS, and images are built once with Astro and stored in a private S3 bucket. Visitors never reach the bucket directly. They reach CloudFront, the AWS content delivery network, which serves cached copies from locations close to them and is the only thing allowed to read from the bucket.

I built this setup by hand in the AWS Console, one service at a time, so I understand what each piece does before automating it.

What happens when you visit

  1. Route 53 answers the DNS lookup for armanzeinali.ca and points it at CloudFront.
  2. CloudFront accepts the HTTPS connection using a free certificate from AWS Certificate Manager (ACM). Plain HTTP is redirected to HTTPS.
  3. A small CloudFront Function runs on every request. It maps clean URLs like /about to the right file, and sends www.armanzeinali.ca to armanzeinali.ca with a permanent redirect.
  4. If CloudFront has the file cached, it returns it right away. If not, it fetches it from S3 using Origin Access Control (OAC), which signs the request so the bucket can confirm it came from this distribution.
  5. CloudFront adds security headers to every response through AWS’s managed security headers policy, such as HSTS and protection against clickjacking and content sniffing. The pages also carry their own Content Security Policy, so only this site’s own scripts and styles can run.

How a change gets published today

  1. I build the site on my computer.
  2. I upload the new files to the S3 bucket in the AWS Console.
  3. I create a CloudFront invalidation, so visitors get the new version instead of cached copies.

In progress

  • Infrastructure as code with AWS CDK. The same setup defined in TypeScript, so it can be reviewed and rebuilt from code.
  • Automated deploys with GitHub Actions. On every push, a workflow would build the site, upload it, and clear the cache, using short-lived AWS credentials through OIDC instead of stored access keys.

Security choices

  • No public bucket. S3 Block Public Access is on, and the bucket policy only allows this CloudFront distribution to read files.
  • HTTPS only. Plain HTTP is redirected, and the bucket policy also rejects any request that is not encrypted.
  • Web application firewall. AWS WAF with Amazon’s default protections, included in the CloudFront plan.
  • Cost alert. My AWS account emails me if charges pass $1.

What it costs

CloudFront runs on the Free flat-rate plan: $0 a month, with no charges for traffic spikes. The Route 53 hosted zone costs $0.50 a month on its own and is covered when it is attached to the plan. The ACM certificate is free, and the site’s few hundred kilobytes in S3 cost effectively nothing.