Adding a website does not always require another stack. For these three sites, I extended the AWS platform already serving cgrarodeo.v39.us: one Application Load Balancer, two existing EC2 hosts, and a Deno controller that owns routing and release coordination. The resume and portfolio reuse that foundation. The interesting work is making the boundaries explicit: which hostname serves which page, who may publish, which release receives traffic, and what a browser should do with a www address. This describes the implementation verified on October 28, 2026.
One front door, three public sites
- stokes91.com serves a minimal resume and the original PDF. It has no login or content database dependency.
- forit.us serves the portfolio, project articles, and interactive demos. Reading is public; its publishing desk requires a verified v39.us Google Workspace identity.
- cgrarodeo.v39.us serves the public rodeo website and retains its existing CGRA authentication and management rules.
Browser
|
| DNS resolves the requested hostname
v
AWS Application Load Balancer
| HTTP -> HTTPS
| TLS certificate selected for the hostname
| www -> canonical hostname
v
Blue / green target groups
| Normal traffic -> active release
| Enrolled canary traffic -> candidate
v
Deno controller: route by hostname
+-- stokes91.com -> resume
+-- forit.us -> portfolio + publishing
+-- cgrarodeo.v39.us -> rodeo websiteThese are not three separate load balancers or three independent production fleets. The ALB selects a target using the release-routing rules; the Deno controller then dispatches the request by its registered hostname. The portfolio and resume have explicit host routes. This distinction matters: sharing an entry point does not mean one site's login should grant access to another site's editor.
A release earns the traffic switch
Blue and green are deployment slots, not permanent labels for production and staging. One receives normal traffic while the other can run a candidate. The existing broker at services.v39.us accepts an exact Git commit, verifies the repository and branch proof, retrieves immutable source, and runs the release workflow on the AWS hosts. An accepted request is only the start; completion is reported by a receipt tied to that revision.
- Validate the submitted source revision and run the required tests and coverage checks.
- Install the candidate without moving ordinary visitors off the current release.
- Check target health, application access, and the configured canary route.
- Collect HTTPS and WebSocket evidence that the candidate is serving the expected revision.
- Switch the normal target-group weights only after the gates pass, then verify the new default route.
The canary uses an enrolled administrator IPv4 address and an independent AWS probe address. That lets the system exercise a candidate while other visitors stay on the current version. A failure before promotion leaves ordinary traffic on the existing release; the staged workflow also records recovery information. For a deliberate rollback through the current delivery contract, I revert the source and submit the new branch head through the same broker. Existing WebSocket connections retain their target until reconnecting, so a weight change should not be described as an instantaneous move of every connection.
Publishing is separate from deploying code
Portfolio articles live in the platform's replicated Redis store rather than inside each release directory. The visual editor saves a versioned content document with a compare-and-set operation: if another editor has saved since the page was loaded, the write is rejected and the browser keeps the unsaved edits. A code deployment does not overwrite published edits with the original seed content. The Redis handoff is coordinated with traffic promotion, including checks before changing the primary. This makes state part of the release design; switching application code alone is not a database rollback.
Google identifies editors, not readers
The portfolio uses a dedicated Google web client and the callback https://forit.us/auth/callback. Its OpenID Connect flow requests only openid and email. Visitors can read every published article and use the demos without signing in; the resume site needs no OAuth connection at all. The existing CGRA and V39 platform clients keep their own roles and configuration. Sharing AWS does not create a shared permission to manage all three sites.
For the forit.us editor, the server checks Google's token signature, issuer, audience, and expiry, along with the flow's state and nonce. PKCE binds the authorization code exchange to the sign-in attempt. The application requires both a verified v39.us email and an exact v39.us hosted-domain claim. The domain hint shown to Google's account chooser is not the authorization check. After verification, the editor receives a host-only Secure, HttpOnly, SameSite=Lax session cookie; content writes also require the expected origin and a CSRF token. The Google client secret stays in the existing AWS application secret, outside the page and source repository.
TLS belongs at the load balancer
The ALB terminates browser TLS using AWS Certificate Manager certificates in the load balancer's region. Its HTTPS listener selects a matching certificate using Server Name Indication, the hostname supplied during the TLS handshake. The three main-site certificates remain attached, and an additional certificate covers the three www aliases. The listener supports TLS 1.2 and 1.3. The backend hop to the Deno controller uses HTTP inside the existing private network boundary; this setup should not be mistaken for TLS extending all the way to the application process.
A redirect cannot repair a certificate mismatch that happens before the HTTP request. https://www.stokes91.com must complete a valid TLS handshake before the ALB can tell the browser to open stokes91.com. That is why both the main hostname and its www alias need certificate coverage. DNS validation records remain published for certificate renewal. At Gandi, the two bare domains use ALIAS records for the ALB and their www names use CNAME records; the rodeo hostname already resolves to the same ALB through its existing DNS.
Canonical redirects preserve the page you asked for
http://stokes91.com/...
-> https://stokes91.com/...
http(s)://www.stokes91.com/...
-> https://stokes91.com/...
http(s)://www.forit.us/...
-> https://forit.us/...
http(s)://www.cgrarodeo.v39.us/...
-> https://cgrarodeo.v39.us/...
Example:
https://www.stokes91.com/resume.pdf?download=1
-> https://stokes91.com:443/resume.pdf?download=1
Response: 301 Moved PermanentlyThe HTTP listener redirects main-site requests to HTTPS. Exact www host rules on both listeners send visitors directly to the corresponding main HTTPS address, preserving the path and query string. The explicit :443 in the generated Location header is the standard HTTPS port. Alias redirects run before the deployment-path and canary rules, so a reserved path cannot accidentally skip canonicalization. The normal blue/green forwarding weights remain under deployment control.
This required fixing real legacy behavior. www.stokes91.com still pointed at an old CloudFront page, while www.forit.us still used a registrar redirect to a different website. The rodeo www name resolved to the ALB but lacked a matching certificate. I backed up the DNS records, issued and attached the alias certificate, installed the redirect rules, and changed the two obsolete CNAME destinations. Existing mail records and the main-site application behavior were preserved.
Verify the hostname, the certificate, and the destination
- Check all six hostnames: the three main addresses and their www aliases.
- Validate the certificate against each requested hostname, with normal certificate verification enabled.
- Verify HTTP-to-HTTPS and www-to-main redirects, including a non-root path and query string.
- Confirm the final page is the intended resume, portfolio, or rodeo site, rather than accepting any HTTP 200 as success.
- Check from outside the workstation network, then repeat through the user's normal connection and browser.
The final checks passed from AWS using ordinary DNS and normal HTTPS verification. Local checks initially disagreed: the workstation's VPN resolver retained a missing-address response for stokes91.com and old destinations for the www names. Reconnecting the VPN refreshed those answers. Safari then followed the secure www redirect and displayed the live resume. No certificate warning was bypassed. Separating DNS resolution, TLS negotiation, redirect behavior, and page content made the failure understandable instead of treating every browser error as an application outage.
The tradeoff is shared responsibility
Reusing the existing hosts and load balancer avoids provisioning another application fleet for a resume and portfolio. It also means the sites share capacity, controller releases, and some failure modes. Host routing and separate login policies provide logical boundaries, not separate infrastructure isolation. The operating discipline has to match that choice: test the shared controller, preserve state compatibility, keep secrets out of releases, and verify every hostname after routing changes. The result is a small platform whose behavior can be inspected from the commit receipt all the way to the browser address bar.