A concept-by-concept walkthrough of the CI/CD, serverless, and DNS techniques behind lelunar.me — written for engineers who want to see how the pieces fit, and maybe borrow the pattern.
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.
Triggered by a push to main.
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.
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.
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.
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.
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.
The home page's visitor counter — the only part of this site that isn't purely static.
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.
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.
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.
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."
How the news page stays current without anyone touching it.
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.
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.
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.
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.
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.
A quick-reference list, grouped by provider.