ReviewOS

stacks/ts-cloud

Test deploy: paweldregan.com — keep CloudFront/S3 frontend, add Lambda-backed /api

#117
Open glennmichael123 opened this 2 months ago ·
2 months ago

Goal

Use paweldregan.com as the first real-world target for a ts-cloud serverless deploy. It's a low-stakes static site that already has the CloudFront + S3 + CloudFormation stack live, so we can layer a Lambda-backed API onto it without rebuilding the frontend.

Once this works, the same pattern becomes the template for other Stacks app deploys.

Current infra (verified)

LayerResource
Domain / DNSpaweldregan.com — nameservers on Porkbun (*.ns.porkbun.com)
CDNCloudFront distribution d2a6p52pzjhw1b.cloudfront.net — aliases paweldregan.com + www.paweldregan.com
OriginS3 bucket paweldregan-com (static site: 404.html, about.html, coaching.html, plus de/, images/, pl/ prefixes for i18n)
IaCCloudFormation stack paweldregan-static-site (UPDATE_COMPLETE, created 2026-05-03)
Accountstacks (923076644019), us-east-1

Status today: static-only. No /api path. No Lambda. No docs path.

Target architecture (from the cloud-cleanup task drop)

                   paweldregan.com (Porkbun DNS → CloudFront)
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
       /*                   /api/*               /docs/*
   (default)            (Lambda origin)       (bunpress origin)
        │                     │                     │
   S3 paweldregan-com    Lambda function        S3 bunpress bucket
   (static, current)     (Stacks API, new)        (later)
  • Frontend (/*): stays as-is — keep the existing S3 origin and CloudFront distribution, don't touch the current static content.
  • API (/api/*): new CloudFront origin pointing at a Lambda function (Function URL or API Gateway HTTP API). Per Chris's earlier preference, Lambda over ECS for cost reasons, if it's simple — fall back to ECS only if Lambda has unsolvable size/cold-start problems.
  • Docs (/docs/*): deferred to a follow-up; this test only needs the frontend + API path.

Scope for this test deploy

  1. Pick a trivial Stacks app to deploy as the API (literal hello-world endpoint is fine — the goal is the plumbing, not the payload).
  2. cd storage/framework/cloud && bun deploy.ts against a config that targets paweldregan.com.
  3. ts-cloud should:
    • Detect / reuse the existing paweldregan-static-site CloudFormation stack (don't blow away the static frontend).
    • Provision the Lambda + its execution role + log group.
    • Add a new origin + cache behavior to the existing CloudFront distribution for /api/* → Lambda.
    • Wire the API route into the Stacks app's router.
  4. Verify:
    • curl https://paweldregan.com/ still serves the static site untouched.
    • curl https://paweldregan.com/api/health (or whatever the hello-world route is) hits the Lambda and returns a 200 with the expected body.
    • Lambda cold-start latency is recorded.
    • Monthly cost estimate vs equivalent ECS deploy, captured in the PR description.

Known constraints / open questions

  • Bun on Lambda: Chris's note from the task drop — "the bun runtime didnt exist, but then bun released a lambda layer that allowed for the bun runtime ... idk if there potential size issues". Worth confirming up front which Lambda layer/runtime ts-cloud generates and whether the 250 MB unzipped size limit is anywhere close to a concern for a Stacks API bundle.
  • Cold start: the cost win of Lambda over ECS is meaningful, but if cold starts are >2s for a Stacks API bundle, we'd want either provisioned concurrency or a fallback to a small Fargate task. Quantify before scaling this pattern to other domains.
  • CloudFormation drift: if ts-cloud manages a new stack alongside the existing paweldregan-static-site stack, document the boundary. If it modifies the existing stack, that needs to be safe and reversible — losing the frontend in this test would be visible to anyone hitting the live domain.
  • DNS unchanged: the test does not require any Porkbun DNS changes — CloudFront fronting the same hostname continues to handle routing. (Unrelated: the hello@paweldregan.com mailbox work in mail-os/mail will need Porkbun DNS changes, but that's a separate thread.)

Out of scope

  • Frontend changes — the existing static site stays exactly as deployed.
  • Docs path (/docs/* → bunpress) — defer.
  • DNS migration off Porkbun.
  • Multi-region. Single us-east-1 Lambda is enough for a first test.

Acceptance

PR opens with:

  • A working deployed paweldregan.com/api/... hitting a Lambda.
  • paweldregan.com/ static site unchanged (no regressions).
  • A short writeup in the PR description covering: what ts-cloud auto-managed vs what was manual; Lambda runtime choice + bundle size; observed cold-start; estimated monthly cost vs equivalent ECS.

cc: @chrisbreuer

8 days ago

Roadmap relationship: tracked by #134 under master roadmap #132.

8 days ago

Implementation progress on main:

  • ccaa431 adds ETag-protected, collision-safe CloudFront origin/behavior patching and exact rollback.
  • ba860d0 adds credential-profile propagation, Lambda origin access controls, dual function URL permission fields, and log retention primitives.
  • b2e9bfc adds the guarded end-to-end private Lambda URL deployment planner/executor, frontend integrity/API/cold-start verifier, cost comparison, rollback metadata, and unit coverage.
  • b14c32d exposes cdn:api:deploy, cdn:api:verify, and cdn:api:cost.
  • 52c0ceb documents architecture, IAM boundary, exact confirmation, verification, POST/PUT payload constraint, cost assumptions, and rollback order.

Verification so far: 6 focused tests pass, package typecheck is clean, CLI bundles 271 modules, and docs build 39 pages.

The first live read-only preflight made no changes and found the locally stored stacks credentials currently return AWS InvalidClientTokenId (403). The same check fails for every locally stored profile, with no AWS credential environment override present. Live deployment and acceptance evidence (distribution ID, unchanged frontend SHA-256, health result, cold-start duration, bundle size, and measured cost comparison) remain pending a refreshed local AWS session.

8 days ago

Implementation and safety verification are complete, but the live apply is blocked before mutation because every local AWS profile (stacks, default, and 11ly) returns InvalidClientTokenId; the repository has no AWS Actions secret fallback. No AWS resource was changed. Public baseline captured 2026-07-22: https://paweldregan.com/ is HTTP 200, SHA-256 ec7894d9885894afb33b1e363fd714ad39cbf994973383346043d4c72a00e053, 225,916 bytes; /api/health has no working response. The guarded implementation and rollback path pass their focused tests, and the full repository suite passes 2,888/2,888 tests. Planned runtime is managed Node.js 22 with an inline single-file handler (no Bun layer or Stacks bundle, therefore nowhere near Lambda’s 250 MB unzipped limit). Directional 100k requests/month at 100 ms and 256 MB: Lambda $0.00 with free tier vs $25.44/month for one 0.25-vCPU/0.5-GB Fargate task plus ALB, excluding transfer/logs/taxes. Resume after refreshing ~/.aws/credentials profile stacks: resolve the exact distribution ID, run the read-only cdn:api:deploy plan, preserve the recorded frontend digest, apply only its exact confirmation token, wait for CloudFront Deployed, then run cdn:api:verify to record health, latency, and Lambda Init Duration.

Sign in to comment on this issue.