ARArchitecture

From deployed website to publishing platform to cost-optimized production.

The first version proved the AWS hosting model. The second separated content publishing from application deployment. The third moved application serving to App Runner while retaining the independent content plane.

Where things stand

Version 3 is now running in production. It replaces the ECS Fargate + Application Load Balancer serving path with AWS App Runner. The v2 CMS connection was restored on October 11, preserving published content and verifying public discovery surfaces.

This migration was driven by cost optimization. The v1 application platform—designed to demonstrate full-stack AWS hosting—accomplished that goal but remained expensive for a personal site. App Runner delivers the same Node SSR application and static assets at a fraction of the cost.

Version 1 — the deployment architecture (Oct 2025 – Oct 2026)

The original goal was to run a real full-stack personal site on AWS without pretending it needed hyperscale complexity. Visitors entered through Amazon CloudFront. Hashed client assets went to object storage; HTML, server functions, RSS, and application routes went through an Application Load Balancer to a Node container on ECS Fargate.

Browser ──► DNS ──► CloudFront (TLS)
                          │
          ┌───────────────┴───────────────┐
          │                               │
    /assets/*                     default (*)
          │                               │
          ▼                               ▼
   S3 object storage              Application Load
   hashed JS / CSS                Balancer
                                          │
                                          ▼
                                   ECS Fargate
                                   Node + SSR app
                                          │
                              ┌───────────┴───────────┐
                              ▼                       ▼
                         DynamoDB                    SES
                      Dispatch signups          notifications

This split-origin design is still sound. Immutable files are cheap to cache at the edge, while requests that need execution reach the application tier. The /assets/* CloudFront behavior must remain more specific than the default behavior or the browser will ask the wrong origin for its CSS and JavaScript.

What v1 taught me

The hosting architecture worked. The publishing architecture was the part that did not scale gracefully.

In v1, an essay is a React/TSX module registered inside the application. Its metadata, body, related links, search entry, RSS entry, sitemap URL, and images all travel with the codebase. Publishing one article or Field Note therefore means:

  1. edit source files and discovery files;
  2. build the client and server bundles;
  3. sync hashed assets to S3;
  4. build and push a Docker image;
  5. register and roll out a new ECS task definition;
  6. invalidate CloudFront and verify production.

That process is appropriate for changing application behavior. It is too much ceremony for publishing prose. It also places content images inside the application artifact, making container pushes larger than they need to be.

Version 2 — the content architecture (2026)

Version 2 introduced a separate content plane. The application asks repository contracts for Writing and Field Notes metadata and bodies. It does not need to know whether the provider is a local Markdown directory or AWS services.

Readers ──► CloudFront ──► existing Node application
                                      │
                                      ▼
                              Content repositories
                                 /           \
                                /             \
                     local Markdown        AWS content plane
                     development adapter   production adapter
                                                │
                                  ┌─────────────┴─────────────┐
                                  ▼                           ▼
                             DynamoDB                         S3
                       metadata + revisions         Markdown + images

Editors ──► Cognito ──► /admin ──► draft / preview / publish
   │                                  │
   └── individual identities          └──► targeted cache invalidation
       and controlled access

The local adapter is the first implementation. It reads validated Markdown and YAML front matter from the repository. Existing TSX essays remain available through a compatibility adapter, so migration can happen one item at a time instead of through a risky all-at-once conversion.

The production adapter now uses DynamoDB for metadata, publication state, and revisions; S3 for Markdown bodies and uploaded media; and Cognito for editor authentication. The same repository contracts feed Writing and Field Notes pages, Search, RSS, related reading where applicable, and SEO metadata.

Why this version was chosen

  • Publishing becomes independent. Writing and Field Notes can be created, previewed, and published without rebuilding or restarting the application.
  • Authorship can become collaborative. Multiple editors can receive individual Cognito identities and controlled CMS access without sharing AWS credentials, repository permissions, or a deployment workstation. Identity and editor attribution also provide the foundation for future roles, approvals, and audit history.
  • The migration stays reversible. The repository boundary lets local Markdown, legacy TSX, and future AWS content coexist.
  • Content remains portable. Markdown is easier to export, review, version, and move than prose embedded in application components.
  • The rendering surface stays controlled. The site supports a safe Markdown vocabulary rather than arbitrary executable MDX or raw HTML.
  • Images leave the container. Moving article media to S3 reduces Docker image size and makes ECR deployments faster and more reliable.
  • One source can feed every discovery surface. Search, RSS, sitemap data, structured metadata, and eventually llms.txt can be generated from the published catalog instead of updated by hand in several places.

Version 3 — the cost optimization (Oct 2026)

Version 3 replaces the v1 application platform with AWS App Runner. The v2 content plane remains unchanged. App Runner is a fully managed container orchestration service that eliminates the need for an Application Load Balancer, ECS cluster management, and most networking configuration.

surujnarine.net ──► Route 53 ──► CloudFront
                                      │
                    ┌─────────────────┴─────────────────┐
                    ▼                                   ▼
             Default + /assets/*                  /cms-media/*
                    │                                   │
                    ▼                                   ▼
               App Runner                         S3 public media
                    │
                    ▼
          Node + SSR + static files
          0.25 vCPU, 512 MB RAM
          auto-scaling 1–3 instances
                    │
        ┌───────────┼────────────┐
        ▼           ▼            ▼
    DynamoDB        S3          SES
    metadata    private bodies  notifications

App Runner handles TLS certificates, health checks, deployment rollouts, and autoscaling. The Node server now serves static files directly from the dist/client directory—hashed assets under /assets/*, content images under /images/*, and root discovery files like llms.txt. The sitemap and RSS feeds are generated from published content at request time.

The canonical origin is surujnarine.net. CloudFront routes the default behavior and hashed assets to App Runner, while CMS media remains on S3. The distribution also has the www alias.

Why v3 was chosen

  • Lower expected hosting cost. The migration plan estimated App Runner at $3–6 per month versus roughly $48–52 for the prior stack. Realized savings depend on usage and removal of legacy resources.
  • Simpler operations. App Runner eliminates load balancer maintenance, ECS task definition updates, security group rules for ALB-to-ECS traffic, and VPC endpoint costs.
  • Deployment stays straightforward. Push a Docker image to ECR through the dev workflow, test it, then manually promote that same artifact to production. Production promotion does not rebuild the image.
  • The content plane remains independent. The CMS connection needed restoration after the hosting migration omitted runtime settings and permissions. Reconnecting it preserved published content and restored publishing through DynamoDB, S3, and Cognito.
  • A staging environment costs pennies. The dev environment at dev.surujnarine.net runs the same App Runner configuration and adds only $3–6/month, making it practical to test changes in production-equivalent infrastructure before cutover.

The tradeoff is less control. App Runner does not expose the underlying compute instances, and its autoscaling configuration is less granular than ECS. For a personal site with modest and predictable traffic, those limitations do not matter.

What changed and what stayed

Replaced in the serving path: The Application Load Balancer and ECS application service. CloudFront now routes the default behavior and /assets/* to App Runner.

Added: App Runner service, static file serving middleware in the Node entry point, and a restored CMS instance-role policy and runtime configuration. The existing CloudFront distribution retains CMS media on S3.

Unchanged: The entire v2 content plane (Cognito, DynamoDB, S3, CMS admin), Route 53 DNS, ECR container registry, SES for email, IAM task role-based access, and the content repository contracts.

Compared with a static-site blog workflow

A conventional repository-backed static blog—whether built with Astro or another static site generator—often treats a new article as a source-code change. An author edits a Markdown file, opens a pull request or pushes to Git, waits for a site build, and publishes a new artifact. That can be wonderfully simple for one technical author.

This architecture chooses a different boundary. Authors work through authenticated CMS accounts; publishing updates the content plane while the application artifact stays unchanged. That makes onboarding additional writers and editors substantially easier and avoids giving every contributor access to the repository and release pipeline.

The distinction is architectural, not a limitation of Astro. Astro can also consume a headless CMS and support multiple authors. The advantage here is that collaboration, identity, and no-rebuild publishing are native properties of this deployed system rather than conventions layered onto a code-backed editorial workflow.

The publishing flow

The deployed v2 workflow is deliberately ordinary:

Sign in ──► write or upload ──► preview ──► publish
                                                    │
                         ┌──────────────────────────┼──────────────────────┐
                         ▼                          ▼                      ▼
                  content becomes live      feeds/search update   targeted CDN clear

Application deployments still exist, but only for application changes. Content changes use the content plane. That is the central architectural decision.

What v1 and v2 taught

The lessons from v1 and v2 carry forward into v3:

  • Task IAM roles remain preferable to credentials. App Runner still uses IAM roles to grant the container access to S3, DynamoDB, and SES without embedding secrets.
  • Build-once discipline still applies. The same Docker image can be tested in dev and promoted to production unchanged.
  • Managed infrastructure is better when it fits. The v1 architecture chose ECS and ALB to demonstrate the hosting model. V3 chooses App Runner because cost and simplicity now outweigh the educational value of manual orchestration.
  • Content independence is load-bearing. The v2 content plane separates publishing from application deployment, but migrations still need configuration and content checks. The October 11 restoration preserved the public catalog and verified all 52 previously indexed detail routes.

Production status

  • V3 in production (Oct 2026): CloudFront serves surujnarine.net through App Runner, with CMS media routed to S3.
  • V2 content plane (restored October 11): Cognito authentication, DynamoDB metadata and revisions, S3 Markdown bodies and media, validated Markdown, draft preview, Writing and Field Notes editing, legacy TSX fallback, Search, RSS, sitemap, and SEO metadata.
  • V1 superseded in the serving path: Legacy resource teardown and billing savings require separate verification. Architecture and cost breakdown archived in docs/ARCHIVE-ecs-architecture.md.
  • Dev environment: dev.surujnarine.net runs the same App Runner stack for testing changes before production cutover.

Cost and operational posture

The migration plan estimates $3–6 per month for App Runner at modest traffic. CloudFront, Route 53, DynamoDB, S3, Cognito, and SES costs depend on usage. These estimates are not a verified bill or confirmation that legacy resources have been removed.

This is sustainable for a personal site. The v1 architecture—$48–52/month—was chosen to demonstrate real AWS application hosting. It succeeded, and then became too expensive to justify. V3 preserves operational discipline (IAM roles, build-once containers, staging environment) without the monthly cost of unused ALB capacity and Fargate minimum task size.

The v2 content plane complexity remains. That complexity is still justified: it separates publishing from application releases, enables collaboration, and keeps Markdown portable. V3 changes application hosting while preserving that boundary.

Related

  • Colophon — stack, fonts, and local tooling.
  • V1 archived documentation: docs/ARCHIVE-ecs-architecture.md — ECS+ALB architecture, cost breakdown, and decommission checklist.
  • V2 content plane design: docs/cms-architecture.md.
  • V3 migration notes: docs/architecture.md — App Runner cost analysis and cutover plan.
  • Discovery maintenance: docs/seo-and-ai.md.

← Lab