TM Taeyang Moon
Demo · Architecture write-up

moontaeyang.com learning portal

A serverless static portal where AI auto-generates content daily and weekly

Static Web AppsContainer Apps JobAzure OpenAI (keyless)Storage $webSocial login
🔗
Live sitemoontaeyang.com
Open site →

This document treats this site(moontaeyang.com) itself as an Azure demo and explains which services it uses, how they are composed, and why it was designed this way from the perspective of engineers who want to build it themselves.


At a Glance

moontaeyang.com is a personal learning portal where AI automatically generates and publishes content daily/weekly. On top of one static web hosting service(Static Web Apps), two scheduled AI agents(Container Apps Jobs) generate and attach content. The key themes are serverless · keyless(no secrets) · schedule-based · static-first.


Architecture

                                  ┌──────────────────────────────────────┐
   User(browser) ──HTTPS──▶        │   Azure Static Web Apps (Standard)    │
   moontaeyang.com                │   - Global CDN + free TLS certificate │
                                  │   - Custom domains(moontaeyang.com,www)│
   Social login ──/.auth/*──▶      │   - Built-in auth(Google·GitHub·Kakao OIDC)│
                                  │   - Static file serving + SPA fallback │
                                  └───────────────▲──────────────────────┘
                                                  │ swa deploy(rebuild entire portal)
                 ┌────────────────────────────────┴────────────────────────────────┐
                 │                                                                  │
   ┌─────────────┴─────────────┐                                  ┌─────────────────┴───────────────┐
   │ Container Apps Job         │  Daily 06:00 KST                  │ Container Apps Job              │  Every Mon 06:00 KST
   │ engitdaily-job             │  (cron 0 21 * * *)                │ azwhatsnew-job                  │  (cron 0 21 * * 0)
   │  1) Collect HN/RSS         │                                  │  1) Collect Azure Updates RSS   │
   │  2) Azure OpenAI summarize/│◀── Managed Identity(keyless) ──▶ │  2) Azure OpenAI summarize     │
   │     generate               │                                  │                                  │
   │  3) Accumulate in Storage  │                                  │  3) Accumulate in Storage       │
   │     $web                   │                                  │     $web                         │
   │  4) Rebuild/deploy portal  │                                  │  4) Rebuild/deploy portal       │
   │  5) Telegram notification  │                                  │  5) Telegram notification       │
   └─────────────┬─────────────┘                                  └─────────────────┬───────────────┘
                 │                                                                  │
   ┌─────────────▼─────────────┐                                  ┌─────────────────▼───────────────┐
   │ Storage(engit) $web        │  Accumulates past daily pages     │ Storage(azwn) $web              │  Accumulates past weekly pages
   │ engitdailyst…              │  (permanent "warehouse")          │ azwhatsnewst…                   │  (permanent "warehouse")
   └────────────────────────────┘                                  └─────────────────────────────────┘

   Shared: each agent has a dedicated ACR(image) · User-Assigned Managed Identity(UAMI) · Container Apps Environment.
   Static sections(/labs, /demos) are included from repository HTML into the portal shell and published with every deployment.

Azure Services Used (by Role)

Service Role Notes
Azure Static Web Apps (Standard) Public web serving, custom domain, TLS, social login Standard is required for custom authentication/OIDC
Azure Container Apps Job Generates content as scheduled(cron) batches 0 instances normally → billed only when running(serverless)
Azure OpenAI (gpt-5.4) Article/summary/vocabulary generation Keyless: called via Managed Identity + RBAC
Azure Storage(Blob, $web) Accumulated storage for generated pages(warehouse) + md history Uses the static website container $web
Azure Container Registry(ACR) Stores agent container images Cloud build via az acr build
User-Assigned Managed Identity ACR pull · Storage read/write · OpenAI call permissions Core of zero-secret operations
Telegram Bot API(external) Publication completion notifications Token only as Job environment variable(secret)

Microsoft Entra ID(app registration) is used to integrate GitHub/Google/Kakao OAuth apps with SWA authentication for social login.


Key Design Points (Why This Way)

1. Why Static Web Apps

2. Why the Standard plan

3. Content "accumulation" and the clobbering problem (most important)

4. Static sections vs dynamic sections

5. Keyless operations

6. Cron schedules use UTC

7. Security guardrails


Data Flow

(A) User request flow 1. Browser requests moontaeyang.com → SWA returns static files from the global CDN(TLS automatic). 2. User clicks login → /.auth/login/<provider> → provider(Google/Kakao/GitHub) authentication → callback → session cookie. 3. SPA fallback: unmatched paths route to shell index.html(except /engit-daily/*,/azure-daily/*,/labs/*,/demos/*,/.auth/*).

(B) Content generation flow(agent, once per day/week) 1. Cron triggers the Job → container starts(image pulled from ACR). 2. Source collection(Hacker News API / Azure Updates RSS). 3. Generate articles, summaries, and vocabulary through Azure OpenAI(keyless). 4. Accumulate results in Storage $web(today's/this week's page + section index updates), and keep md history in Blob. 5. Portal rebuild: copy shell(+static sections labs/demos) + mirror the two dynamic sections from each $webswa deploy. 6. Send a "publication complete + link" notification through Telegram.


Cost Overview (Approximate, Personal Traffic)


Build Sequence for Engineers (Summary)

  1. Create SWA(Standard) + connect custom domains(moontaeyang.com, www) + DNS(A/ALIAS, TXT verification).
  2. Social login: register Google/GitHub/Kakao OAuth apps → add an auth block to SWA staticwebapp.config.json(+ customOIDC for Kakao) → inject client-id/secret into SWA app settings(otherwise /.auth/* returns 404).
  3. Agent infrastructure(1 set each, Bicep): ACR + UAMI + Container Apps Environment + Job(cron) + Storage($web static website enabled) + Azure OpenAI(deployment gpt-5.4).
  4. RBAC: grant UAMI AcrPull · Cognitive Services OpenAI User · own Storage Blob Data Contributor · other Storage Blob Data Reader(mirror).
  5. Image build/deploy: build image with az acr build → update Job(environment variables: PORTAL_SECTIONS, PORTAL_BASE_URL, SWA_DEPLOY_TOKEN(secret), etc.).
  6. Deployment logic: at runtime, the container copies shell+static sections + mirrors dynamic sections + runs swa deploy.
  7. Notifications: Telegram Bot token/chat-id as Job environment variables.
  8. Add static sections: build /labs, /demos from Markdown to HTML and include them in the portal shell.

Common Roadblocks / Lessons Learned


References

← All demosPortal home