The big picture

There is no server running anywhere for this site — no process to patch, restart, or lose sleep over. It's a static frontend (plain HTML/CSS/JS, zero build tooling) backed by a handful of managed, pay-per-use cloud primitives, wired together by CI/CD instead of manual deploys. One git push fans out into two independently hosted copies of the site, plus a scheduled job that keeps the news page fresh — all from a single public repo: techtesfay/techtesfay.github.io.

This pattern — static assets on a CDN, small bits of dynamic behavior via client-side calls to serverless functions — is sometimes called the JAMstack (JavaScript, APIs, Markup). The three sections below walk through deployment, the one dynamic feature (a visitor counter), and a scheduled automation job, each pairing a general concept with exactly how it's used here.

1. Deploy pipeline

Triggered by a push to main.

Flowchart: a local edit is pushed to the GitHub repo, which triggers a Push to S3 workflow (S3 bucket lelunar, then CloudFront with a 5-second cache, then Cloudflare DNS, then lelunar.me) and a GitHub Pages build (then techtesfay.github.io). A separate scheduler runs the Refresh news feed workflow every 6 hours, which pulls from 9 RSS feeds and commits news.json back to the repo, re-triggering both deploy paths. Flowchart: a local edit is pushed to the GitHub repo, which triggers a Push to S3 workflow (S3 bucket lelunar, then CloudFront with a 5-second cache, then Cloudflare DNS, then lelunar.me) and a GitHub Pages build (then techtesfay.github.io). A separate scheduler runs the Refresh news feed workflow every 6 hours, which pulls from 9 RSS feeds and commits news.json back to the repo, re-triggering both deploy paths.
1
GitOps

A push is the deploy trigger

Concept: Git is the single source of truth. Instead of deploying by hand, a CI system watches the repo and reacts to changes — no SSH-ing into a machine, no manual upload.

Here: GitHub Actions listens for on: push and spins up a throwaway Ubuntu container to run the job.

2
Object storage

Files land in storage, not a server

Concept: A static site has no backend process to run — it just needs files served over HTTP. Object storage is built exactly for this: cheap, durable, effectively infinite storage billed by the byte, not by server-hours.

Here: aws s3 sync . s3://lelunar --delete mirrors the repo into the bucket, deleting anything that's been removed locally so the bucket never drifts from Git.

3
CDN / edge caching

A CDN caches it close to visitors

Concept: Without a CDN, every request travels to the bucket's one physical region. A CDN caches copies at edge locations worldwide so requests are served from whichever is closest — lower latency, less origin load. The tradeoff is staleness: cached content can lag the origin until its cache lifetime (TTL) expires.

Here: CloudFront sits in front of S3. Every synced object is tagged Cache-Control: max-age=5 — a 5-second TTL, short enough that changes are effectively live within seconds while still getting real edge-caching benefit for the far more common case of repeat requests inside that window.

4
DNS

DNS resolves the domain — with a twist

Concept: The obvious way to point a domain at a CDN is a CNAME record. But DNS forbids a CNAME on a zone's apex (lelunar.me itself, not www.lelunar.me) — the apex has to coexist with other record types, like MX for mail. Providers work around this with CNAME flattening: an apex record that behaves like a CNAME to clients while staying spec-compliant.

Here: Cloudflare hosts DNS for lelunar.me in "DNS only" mode — resolving names, not proxying traffic. Response headers confirm it: they show CloudFront and S3 directly, with no Cloudflare proxy signature in front.

5
Redundancy

A second, independent path: GitHub Pages

Concept: The same push can trigger more than one deploy target. It's a useful way to compare two hosting models for the same job: assembling cloud primitives yourself (S3 + CloudFront + DNS) versus an all-in-one managed static host tied straight to a repo.

Here: GitHub Pages auto-builds main's root and serves it at techtesfay.github.io — no DNS or CDN configuration of its own, and deliberately kept independent of the AWS path.

2. A static site with one dynamic sliver

The home page's visitor counter — the only part of this site that isn't purely static.

Flowchart: the browser calls fetch() on page load, hitting Amazon API Gateway, which calls a Lambda function, which reads and writes a DynamoDB counter table, and the count flows back through Lambda and API Gateway to the browser as JSON. Flowchart: the browser calls fetch() on page load, hitting Amazon API Gateway, which calls a Lambda function, which reads and writes a DynamoDB counter table, and the count flows back through Lambda and API Gateway to the browser as JSON.
1
JAMstack

The client calls out to an API

Concept: A page can be 100% static HTML and still be dynamic, by having client-side JavaScript call an API at runtime — instead of a server rendering a fresh response per request.

Here: on load, an inline script in index.html fetches a public HTTPS endpoint and renders whatever it returns. If the call fails, the counter element just stays hidden — the page never depends on it to work.

2
Managed API layer

API Gateway is the HTTP front door

Concept: A function has no public endpoint of its own. A managed API layer turns it into a real HTTPS API — handling routing, CORS, TLS, and throttling — without provisioning or patching a server.

Here: the endpoint is Amazon API Gateway — not to be confused with Transit Gateway, a completely different AWS service for interconnecting VPCs and on-prem networks. There's no VPC or private networking layer anywhere in this stack; it's all public serverless HTTP.

3
FaaS

Lambda — function as a service

Concept: The function only runs when invoked, scales to zero between calls, and is billed per invocation rather than for an always-on server. At personal-site traffic, the cost rounds to nothing.

Here: the function reads the current counter value, increments it, writes it back, and returns the new value as JSON.

4
NoSQL

DynamoDB, reduced to one row

Concept: DynamoDB is a managed key-value store built for scale — but the same primitives (a table, a primary key, an item) work fine at the smallest scale too.

Here: a single item holds one counter attribute that gets read and rewritten on every page load. That's the entire "database."

3. Scheduled automation, no server required

How the news page stays current without anyone touching it.

1
Serverless cron

A schedule can trigger a job, not just a push

Concept: Not every job is a response to a user request or a code change. CI systems' schedule: trigger provides free, serverless cron — handy for periodic work (refresh a cache, run a digest, poll a feed) that has nothing to do with deploying code.

Here: a workflow runs every 6 hours and executes a small Python script — standard library only, no dependencies to install in CI.

2
Syndication

RSS — old, but still the right tool

Concept: RSS/Atom predates most of the modern web, but it remains the simplest way to pull structured updates from a source without an API key or a bespoke integration per site.

Here: the script pulls headlines from 9 free RSS feeds — cybersecurity news, AI news, and arXiv research feeds — merges them, and sorts by publish date.

3
Git as a datastore

Committing the result, instead of a database

Concept: For data that's small and changes infrequently, committing a generated file to Git can be simpler than standing up an actual database — you get versioning, diffing, and hosting for free.

Here: the script writes news.json and the workflow commits it if it changed. That commit is itself a push to main — so it re-triggers Section 1's entire deploy pipeline, and the refreshed headlines reach both live sites automatically.

4
Same-origin fetch

The page reads its own data file

Concept: Fetching JSON from the same domain the page is served from sidesteps CORS entirely — no headers to configure, no preflight requests.

Here: news.html fetches news.json client-side and renders it, with a category filter applied in the browser.

Takeaway

Total infrastructure cost for all of this is a few cents a month at personal-site traffic — GitHub Actions and Pages are free for public repos, and S3 / CloudFront / API Gateway / Lambda / DynamoDB are all effectively free at this scale. Nothing here needs patching, and the entire setup — down to the exact commands — is readable in the repo: workflows, fetch_news.py, and the README.

Every component, at a glance

A quick-reference list, grouped by provider.

GitHub

AWS

Cloudflare