On July 21, 2026, I asked my AI operating system to move JeffBilbrey.com off Replit.
I did not hand it a cloud architecture, migration checklist, or sequence of terminal commands. I gave it the business outcome and the boundaries:
- get the site onto low-cost practical infrastructure I independently own;
- preserve the application, content, media, admin tools, and integrations;
- put explicit cost controls in place;
- do not interrupt the live site before a replacement is proven;
- stop for approval at billing, authorization, DNS, and retirement gates;
- leave a verified rollback path.
The result was not a staged demo. It was a real production migration involving compute, a database, object storage, secrets, DNS, TLS, scheduled jobs, Google integrations, and a public AI feature.
It also exposed a final Replit dependency after launch—and repaired that too.
Outcome at a glance
- Two focused pull requests moved the application and repaired the hidden AI dependency.
- All 12 database tables, 66 rows, and 48 referenced media objects matched.
- The old publication and data remained available as rollback assets during cutover.
- The final public site, health endpoints, uploads, chatbot, DNS, and TLS were verified.
- The sequence from first migration commit to final repaired merge took 4 hours and 13 minutes.
Why I moved it
Replit had been useful for getting the site built, but it no longer made sense as the long-term development and production platform. The cost was too high for how I wanted to work, and too much of the runtime was tied to services owned or abstracted by that environment.
I opened a separate migration goal: evaluate a safe, independently owned replacement, then move the site.
What bounded autonomy actually meant
Autonomy did not mean the system had unlimited authority.
I approved consequential boundaries: connecting the cloud project to billing, authorizing a new Neon project, creating cloud resources, changing public DNS, and retiring the Replit publication.
The system did not reuse an old approval or assume that permission to inspect meant permission to cut over.
Inside those gates, the AI team handled the operational work:
- inventorying the live system;
- selecting the target architecture;
- writing the migration and deployment tooling;
- changing application code;
- diagnosing build failures;
- provisioning and configuring services;
- copying and verifying data and media;
- deploying previews;
- preparing rollback evidence;
- changing DNS after approval;
- validating the public launch;
- documenting the final operating model.
That is the distinction I care about: bounded agency, not unsupervised access.
First, it established what was real
Before proposing a destination, the system inventoried the production baseline.
The Replit deployment was an Autoscale application configured for two vCPUs, 4 GiB of memory, and up to three instances. Behind the public site was more than a collection of HTML pages:
- a server-rendered React frontend;
- an Express API mounted on the same origin;
- an admin CMS;
- a Replit-managed Neon PostgreSQL database;
- Replit object storage with signed upload behavior;
- Google OAuth and Calendar integration;
- contact and project-inquiry forms;
- a booking workflow;
- a daily reminder process;
- Cloudflare DNS;
- public LifeOS policy pages required for Google OAuth.
The data inventory produced exact cutover assertions:
- 30.08 MB PostgreSQL database;
- 12 public tables;
- 66 total rows;
- 48 referenced media objects;
- 12,636,179 total media bytes;
- all 48 existing objects returning HTTP 200 before migration.
Those numbers became release gates. The migration could not call itself complete if the destination merely “looked right.”
Why the answer was not simply “put it on Vercel”
Vercel was an obvious option to explore, but the workload mattered more than the logo on the hosting dashboard.
JeffBilbrey.com is a stateful, same-origin application. Its server-rendered frontend and Express API share a runtime contract. It needs database access, signed media uploads, an admin surface, OAuth callbacks, and a scheduled reminder job.
The selected architecture was:
- Google Cloud Run for the web application, with request-based billing and scale-to-zero behavior;
- Neon Free for an independently owned PostgreSQL database;
- Google Cloud Storage for public media and signed uploads;
- Google Secret Manager for one pinned runtime-configuration object;
- Artifact Registry and Cloud Build for reproducible container builds;
- Cloud Run Jobs and Cloud Scheduler for the 8:00 AM Eastern booking-reminder process;
- Cloudflare for DNS, with Google-managed origin TLS.
Cost control was designed into the deployment. Cloud Run uses zero minimum instances, one vCPU, 512 MiB of memory, and an aggregate maximum of two instances. A project-specific $5 monthly budget alert provides an early warning.
That budget is deliberately described as an alert, not a hard cap. The instance ceiling is the actual runaway-compute guardrail.
The system built the migration machinery
The migration branch did not contain only a Dockerfile.
It added:
- a production container for the SSR frontend and Express API;
- a Cloud Run entry point that expands a pinned Secret Manager JSON value into process-only environment variables;
- a deploy script that provisions and updates the web service, bucket, secret, reminder job, and scheduler;
- a PostgreSQL clone script with destructive-target confirmation and table-by-table row-count validation;
- a storage migration script with object and byte-parity checks;
- Google Cloud Storage CORS configuration;
- a dedicated reminder command so the web service did not need to stay warm;
- a hosting specification and full cutover/rollback runbook.
It also preserved transition compatibility. During preview and DNS migration, the old and new web runtimes could still use the same source database. The independently owned Neon database became authoritative only after public traffic had moved, avoiding a split-brain window where form submissions could land in two places.
Then production did what production does
The first Cloud Build did not magically succeed.
It exposed assumptions that had been hidden by the Replit workspace:
- cloud CLI commands that were not portable enough;
- a build that was not fully reproducible;
- package-manager enforcement missing from the container build layer;
tsxavailable during development but missing at runtime;- server-rendering dependencies that had not been bundled into the production image.
The system diagnosed each failure, changed the implementation, committed the fix, rebuilt, and continued. Five focused corrective commits followed the first migration implementation before cutover evidence was recorded.
This is one of the clearest differences between an AI demo and an agentic engineering workflow. A demo shows the happy path. A useful system remains inside the problem when the happy path ends.
The cutover was evidence-driven
The new Cloud Run service was verified before public DNS changed.
The release checks included:
- the homepage and required LifeOS routes returning HTTP 200;
/api/healthzreturning HTTP 200 with{"status":"ok"};- admin access working;
- signed upload, upload, and public-serving behavior working end to end;
- 12-table and 66-row database parity;
- 48-object and 12,636,179-byte storage parity;
- the intended exact Cloud Run revision serving traffic;
- zero error-level logs after deployment.
Only then, after I approved the cutover, did the system replace the relevant
Cloudflare website records with Google's exact domain-mapping records. It kept
them DNS-only while the managed certificate validated, verified both the apex
and www domains, and confirmed TLS and application health from the public
internet.
The independently owned Neon database was then made authoritative through a new pinned secret version and Cloud Run revision.
Finally, the Replit publication was shut down. Its former deployment URL returned HTTP 404, but the old workspace and database were deliberately retained as temporary rollback assets. Retirement did not mean immediate destruction.
The last hidden Replit dependency
At that point, the website was live on the new infrastructure. Pages rendered, data matched, uploads worked, DNS was correct, and health checks were green.
But the chatbot failed.
The widget accepted a message, created a conversation, and then displayed a generic error. Cloud Run showed an upstream model failure.
The root cause was a perfect migration lesson: the chatbot still pointed to a
Replit-private model proxy on localhost. That sidecar had existed implicitly
inside Replit and did not exist inside the Cloud Run container.
In other words, the code had moved, but one platform assumption had not.
The system treated this as a public trust defect. A feature that claimed to be online but failed every answer could not remain that way.
The follow-up repair:
- removed the Replit-local proxy;
- connected the server directly to the OpenAI Responses API;
- made the production model configurable rather than platform-bound;
- rebuilt the assistant as a dynamic RAG system using current case studies, portfolio entries, published blog posts, and videos from the CMS;
- returned public evidence cards with answers;
- added signed conversation tokens, rate limits, safe provider errors, and privacy-preserving safety identifiers;
- required explicit contact intent plus a visitor-provided name and email before the server could capture a lead or reveal availability;
- removed the exact failed user message when the model request did not complete.
Five retrieval and security tests passed. Type-checking and the production build
passed. A live production prompt returned a grounded answer using current CMS
content. The public widget reported AI ready and produced zero browser-console
errors.
No test email, lead, availability action, booking, meeting, reminder, or calendar event was created. Test-only diagnostic conversations were removed, and no visitor conversation was touched.
The governed delivery chain
The migration and the chatbot repair each traveled through a focused Git branch and pull request.
The migration became PR #3. Its exact PR head was deployed, production evidence was attached, and it was squash-merged only after the data, storage, DNS, TLS, upload, route, health, and log checks passed.
The chatbot repair became PR #4. Its exact PR head was tested locally, deployed, tested through the public domain, visually inspected, and then squash-merged. In both cases, the squash commit's complete source tree matched the deployed PR-head tree.
The timeline is worth noting:
| Time (Eastern) | Event |
|---|---|
| 2:53 PM | First migration implementation commit |
| 4:50 PM | Verified cutover evidence committed |
| 4:56 PM | Migration PR #3 squash-merged |
| 5:37 PM | Direct OpenAI/RAG chatbot repair committed |
| 7:05 PM | Repaired production PR #4 squash-merged |
From the first migration commit to the final repaired production merge, the full sequence took 4 hours and 13 minutes.
That speed did not come from skipping controls. It came from the system being able to perform the next bounded step as soon as evidence allowed it.
The result
JeffBilbrey.com now runs on independently owned infrastructure:
- Cloud Run compute;
- Neon PostgreSQL;
- Google Cloud Storage;
- Secret Manager;
- Artifact Registry and Cloud Build;
- Cloud Run Jobs and Scheduler;
- Cloudflare DNS.
The site scales to zero, has a two-instance aggregate ceiling and a $5 budget alert, retains an explicit rollback path, and no longer depends on Replit for production publishing, data, storage, or AI inference.
As of July 22, both the apex and www health endpoints still return HTTP 200.
What I learned
1. The hard part of a platform migration is not moving code
The hard part is discovering which assumptions the old platform made invisible: storage sidecars, model proxies, development-only dependencies, build behavior, scheduled processes, and credential delivery.
2. Parity is more useful than confidence
“The site looks fine” is not a migration test. Twelve tables, 66 rows, 48 objects, and 12,636,179 bytes gave the system an objective definition of done.
3. Rollback is part of launch, not evidence of doubt
The old workspace and database remained intact while the new origin proved itself. The system stopped publishing from Replit without immediately destroying the recovery path.
4. Autonomous systems need narrower authority, not vague trust
I did not need to execute the migration. I did need to own the intent and approve the consequential actions. Billing, external authorization, public DNS, and retirement remained human gates.
5. Failure is part of the proof
The chatbot bug made the story more honest and more useful. The system did not hide the miss. It traced the failure to a platform-specific dependency, repaired the architecture, verified the public behavior, and updated the operational handoff.
The larger point
I am not interested in building AI that merely talks about work.
I am interested in systems that can take a real objective, understand the environment, build what is missing, recover from production failures, respect authority boundaries, and leave behind evidence another person can audit.
Yesterday, the system did not just recommend a hosting provider.
It migrated its own public proof surface to independently owned infrastructure, launched it, found the assumption it missed, repaired it, and proved the final state.
That is what practical agentic AI looks like to me.
Public verification:
Jeff Bilbrey