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.
Request path: what happens when someone visits
Visitor
Opens armanzeinali.ca
Route 53
DNS points the domain at CloudFront
CloudFront
HTTPS with an ACM certificate. A CloudFront Function maps clean URLs and redirects www. Adds security headers
Origin Access Control
Signs each request to the bucket
S3 bucket
Private. Only this CloudFront distribution can read it
Publish path today: how a change goes live
Build
Astro builds the static site on my computer
Upload to S3
New files uploaded to the private bucket in the AWS Console
Invalidate
A CloudFront invalidation clears cached copies
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
- Route 53 answers the DNS lookup for armanzeinali.ca and points it at CloudFront.
- CloudFront accepts the HTTPS connection using a free certificate from AWS Certificate Manager (ACM). Plain HTTP is redirected to HTTPS.
- 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.
- 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.
- 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
- I build the site on my computer.
- I upload the new files to the S3 bucket in the AWS Console.
- 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.