All projects

Static hosting on AWS

Portfolio Site

This website, hosted on AWS for less than a dollar a month and run like a production service: infrastructure in Terraform, deployments without stored keys, strict security headers, and visitor statistics without a tracking script.

2repositories: site and infrastructure
< $1a month to host, statistics included
< 1 minfrom git push to the live site
0AWS keys stored in the pipeline
0cookies or tracking scripts

Architecture

Two private buckets behind CloudFront

Portfolio architectureVisitorsCloudflare DNS → HTTPSCloudFrontSecurity headers · edge functionVisitor statisticsAccess logs → CloudWatch/*/resume/*Site bucketPrivate · replaced on each deployResume bucketVersioned · one-minute cachepublishesuploadsportfolio-siteGitHub Actions · OIDC roleResume PDFOne aws s3 cp commandportfolio-infraTerraform createsCloudFront and its functionBoth buckets, the certificateDNS records in CloudflareThe pipeline's OIDC roleLogs and the dashboard

What it is

The site you are reading. It is small, but it is hosted and delivered with the same care as a production service, and every part of it is code in two private repositories, one for the site and one for its infrastructure.

How it fits together

  • Astro builds plain HTML and CSS. The few scripts are small files served from the site itself, so the Content Security Policy can block everything else.
  • Terraform creates two private S3 buckets, the CloudFront distribution, the certificate, and its DNS records in Cloudflare. CloudFront reads the buckets through origin access control, and a small edge function redirects www and serves clean addresses.
  • GitHub Actions checks the types and content, builds the site, publishes it, and refreshes CloudFront on every push to main. It signs in to AWS through OIDC with a role that can only write to the site bucket and refresh this one distribution, so no AWS keys are stored anywhere.
  • The resume lives in its own versioned bucket with a one-minute cache. Replacing it is one command, needs no deployment, and keeps the ten earlier versions.
  • Visitor statistics come from CloudFront’s own access logs, delivered to CloudWatch Logs and summarized on a dashboard: visitors per day, countries, the sites that sent them, and which application links were opened. The logs are deleted after 30 days.

Decisions and problems

  • Count real browsers, not requests. The first count showed 23 visitors when only one was real. New domains appear in the public certificate logs, and scanners fetch their pages while pretending to be browsers. They download only the HTML, so the dashboard now counts a script that every real browser loads.
  • No web application firewall. There is no server-side code to protect, and a WAF would cost more than everything else together. CloudFront includes Shield Standard against network attacks.
  • Plain resources instead of community modules, because the whole configuration is small enough to read in one sitting.
  • Checks on every change. The infrastructure repository scans for secrets with Gitleaks and checks the Terraform with TFLint and Checkov, with every skipped rule listed next to its reason. The site pipeline validates every content file against its schema before it builds.

Stack

What it is built with

Site

  • Astro
  • TypeScript
  • CSS
  • Canvas

AWS

  • S3
  • CloudFront
  • CloudFront Functions
  • ACM
  • IAM
  • CloudWatch

Infrastructure

  • Terraform
  • Cloudflare DNS

Delivery

  • GitHub Actions
  • OIDC

Checks

  • Gitleaks
  • TFLint
  • Checkov
  • astro check