Solo Builder: from vibe-coded app to live product
This arc is for one person with an app that already exists — built by hand or built with an AI coding tool — and no cloud experience to go with it. The nine stories follow Sam from a landing page sitting in a repository to a product with customers, a database, a chat channel and a mobile app, and they build on each other in order; each one also stands alone, so start wherever you recognise yourself. The through-line never changes: every change to your infrastructure arrives as a pull request — a proposed change you read and approve before anything is created — with the estimated cost attached to it. Undoing a change is the same button as undoing code, because the change was code.
1. Ship your first site
The win: Your site is live in about five minutes, and the price was on the change before you said yes.
| Who this is for | A solo builder with a frontend in a repository and nowhere to put it |
| Difficulty | Beginner |
| Time | ~5 minutes |
| What you need first | A repository containing a static or buildable frontend. You do not need a Google Cloud project ready — Infrastream can create one for you. |
| What it costs | Nothing per visitor if the site keeps no secrets — it serves from the cache. If one secret is involved, that adds one small always-on component, and the estimate appears on the change before you approve it. |
In plain words
Sam has a landing page in a repository and nowhere to put it. Every tutorial about "deploying to the cloud" opens with a wall of acronyms before a single pixel goes live, so Sam skips all of it: point Infrastream at the repository, and it reads the project, proposes a deployment, and shows the estimated monthly cost inside the pull request, before anything is provisioned. Sam approves it, and a few minutes later the site is live behind a global CDN — a network of caches around the world that holds a copy of your files close to whoever asks for them. The number on the first invoice was the same number that was on the pull request.
It comes down to one question: does anything your frontend does need to keep a secret?
A secret is something like a paid API key — a password your app uses to call somebody else's service. Anything your site sends to a visitor's browser can be read by that visitor: they open the browser's developer tools and there it is. So a secret cannot live in the browser, which means the call that uses it cannot happen in the browser either.
If nothing needs a secret — a plain HTML/CSS/JavaScript site, or an app that only calls public services, or services that accept a token that is safe to hand out because it is short-lived and scoped to that one visitor — then every file is just handed out from the cache. Nothing has to be switched on and waiting between visits. That is the cheapest shape, and it is still fully public.
If one thing needs a secret, that one call has to move off the visitor's machine and onto a computer you control, which holds the key and makes the call on your app's behalf. Infrastream provisions exactly one small piece of software running on a server for that — not a whole backend, just the one place the secret lives. Your site stays cached and cheap; that single piece is the only thing added, and it only appears when there is genuinely a secret worth protecting.
Sam's landing page has no secrets to hide, so it stays in the cheaper shape. The day it needs to call a paid API with a key attached, that one call moves behind the small server-side piece, and the estimate on the pull request changes before the invoice does.
What you actually write
One Application manifest — a manifest is simply the plain-text file that describes what you want
— naming the build that produces your site and the project it runs in.
# .../application-set/samsapp/application/landing-page.yaml
apiVersion: lowops.manifests.v1
kind: Application
metadata:
name: landing-page
application-set: samsapp
release-track: main-track
organizational-unit: web
organization: samsapp
spec:
description: "Marketing site and waitlist landing page."
buildDefinition: landing-page # the BuildDefinition that builds this repository
target: CLOUD_RUN # CLOUD_RUN | KUBERNETES | COMPUTE
meshStrategy: DISABLED
project: samsapp-prod
A matching DeploymentConfig sets the per-environment details — which version is live, scaling
floor and ceiling, ports and health checks — so the same application can run differently in
development and production without touching the manifest above.
What the platform handles for you
- The build pipeline that turns your repository into a runnable image, so you never write CI configuration by hand.
- The global CDN and TLS certificate in front of the site, so visitors get an HTTPS address that loads quickly wherever they are.
- A generated hostname for the service, so the site has a working address on day one and the custom domain becomes an optional step rather than a blocker.
- The cost estimate attached to the pull request, so approving the change and approving the price are the same action.
Learn more
- Deploying an Application
- Application Delivery samples
- Application manifest reference
- DeploymentConfig manifest reference
2. Get your own domain, without leaving your repo
The win: A real domain from one merged line — and nothing to renew by hand in six months.
| Who this is for | Anyone whose site works but whose URL is not presentable |
| Difficulty | Beginner |
| Time | ~10 minutes |
| What you need first | Story 1 done, and a domain you already own at a registrar |
| What it costs | Nothing extra beyond the front door you already have; the certificate is issued and renewed by the platform. |
In plain words
The generated URL works, but sams-landing-page-4821.run.app is not going on a business card. Sam
owns samsapp.dev, bought years ago for a project that never shipped. Adding it is a one-line
change: Infrastream opens a pull request that binds the domain, creates the DNS zone — the place
that answers the question "which server is samsapp.dev?" — and requests the HTTPS certificate. Sam
merges it, points the domain's nameservers at the zone the platform created, waits for the
certificate to issue, and the domain resolves. There is no control panel with seventeen tabs, and
nothing to renew manually later.
What you actually write
A PublicIngress manifest — the public front door for everything in this project — with the domain
you own.
# .../project/samsapp-prod/public-ingress/front-door.yaml
apiVersion: lowops.manifests.v1
kind: PublicIngress
metadata:
name: front-door
project: samsapp-prod
environment: production
organizational-unit: web
organization: samsapp
spec:
description: "Public front door for the marketing site."
domain:
name: samsapp.dev # base domain; the platform creates the DNS zone and certificate
txtRecords: # optional extras kept at the zone apex, e.g. domain verification
- "google-site-verification=xxxxx"
Routes attach to this front door and decide which path goes to which application — that is the next story.
What the platform handles for you
- A managed DNS zone with the records the platform needs, so you are not hand-copying record values between two dashboards.
- The TLS certificate and its renewals, so the padlock keeps working without a calendar reminder.
- The external load balancer the domain points at, so every route you add later inherits the same address and certificate.
- Your extra TXT records are re-applied on every run, so platform-managed records never quietly erase the ones you added for domain verification or email.
You must delegate the domain at your registrar by pointing its NS records at the nameservers of the zone Infrastream created. Until you do, the certificate cannot validate and the domain will not resolve.
Learn more
3. Put a login on exactly one page
The win: A private
/adminpage for a one-person project, with no authentication code written.
| Who this is for | A builder who needs one private page and does not want to build a login system |
| Difficulty | Beginner |
| Time | ~15 minutes |
| What you need first | Story 1 done, and a Google Workspace or compatible identity you can sign in with |
| What it costs | Nothing extra — the gate rides on the front door and the application you already run. |
In plain words
Sam wants an /admin page for editing content without redeploying, but building an authentication
system for a one-person side project is how side projects die. This is an internal-access problem,
not a customer-facing one, so Infrastream puts Identity-Aware Proxy — a checkpoint that sits in
front of the app and refuses to pass the request along until it knows who you are — in front of
/admin only, tied to Sam's own account. The rest of the site, the part strangers actually visit,
stays exactly as public as it was. There is no session logic to write and no password to forget.
What you actually write
Two rules on one route. The first rule names the identity that may pass; the second has no
authentication block at all, which is what keeps the public site public.
# .../public-ingress/http-route/site-routes.yaml
apiVersion: lowops.manifests.v1
kind: HttpRoute
metadata:
name: site-routes
public-ingress: front-door
project: samsapp-prod
environment: production
organizational-unit: web
organization: samsapp
spec:
rules:
- matches:
- prefixMatch: /admin # only this path is gated
authentication:
- type: IDENTITY_PROVIDER
tenants: [admin-login] # an IdentityProvider manifest
action:
destinations:
- deploymentConfig: landing-page
port: 8080
- matches:
- prefixMatch: / # no authentication block: stays public
action:
destinations:
- deploymentConfig: landing-page
port: 8080
Who is allowed through is named on the front door's iapPermissions block, which lists
OrganizationUserGroup manifests rather than raw email addresses — so revoking access later is
removing a person from a group, in a diff anyone can read.
What the platform handles for you
- The Identity-Aware Proxy checkpoint and its IAM bindings, so an unauthenticated request never reaches your container at all.
- Per-path enforcement, so one page can be private while the rest of the site stays open — you are not choosing between "all public" and "all private".
- The hosted sign-in page, so there is no login screen to design or session cookie to get wrong.
- Group-based access, so "who can reach the admin page" is a reviewable line rather than a setting buried in a console.
Learn more
- HttpRoute manifest reference
- PublicIngress manifest reference
- Access & Identity samples
- Managing Access
4. Turn your site into a real app
The win: What used to be four tickets to a platform team is one merged pull request.
| Who this is for | A builder whose site now needs to remember things between visits |
| Difficulty | Intermediate |
| Time | ~30–45 minutes |
| What you need first | Story 1 done, backend or API code you can deploy (or the willingness to scaffold one), and a decision on the database engine |
| What it costs | Scales with the size you pick for the database, and a database is exactly what the off-hours schedule can pause — see Controlling Costs. |
In plain words
The landing page did its job and people signed up for a waitlist. Now Sam needs somewhere to put them. The usual version of this involves provisioning an API service, then a database, then the private network path between the two, then the permissions that let one talk to the other — four separate pieces of work that each have to agree with the other three. Instead, Sam describes the shape of the app once: a frontend, an API, and a managed PostgreSQL database. Infrastream provisions all of it as one connected graph, where the API already knows how to reach the database and the database already trusts only the API.
What you actually write
A Database manifest declares the instance and the logical databases on it. The API then asks for
access in its own manifest, and that request is what wires the two together.
# .../application-set/samsapp/application/waitlist-api.yaml
apiVersion: lowops.manifests.v1
kind: Application
metadata:
name: waitlist-api
application-set: samsapp
release-track: main-track
organizational-unit: web
organization: samsapp
spec:
description: "Waitlist and account API."
buildDefinition: waitlist-api # the BuildDefinition that builds this repository
target: CLOUD_RUN
project: samsapp-prod
accessControl:
database:
name: accounts-db # the Database manifest in this project
schema: accounts # a logical database declared in its spec.databases
privileges: [USAGE, CREATE]
secretSource:
envVar: DATABASE_URL # credentials injected at runtime, never written to Git
The companion Database manifest sizes the engine — for AlloyDB (PostgreSQL), cpuCount and
clusterSize — and lists the logical databases applications may connect to by name.
What the platform handles for you
- The private network path between the API and the database, so the database is never reachable from the public internet.
- A dedicated database user for this application with exactly the privileges listed, so a mistake in one service cannot rewrite everything.
- The credentials themselves: created, stored in Secret Manager, and injected as
DATABASE_URLat runtime, so no password is ever committed to your repository. - The ordering between the pieces, so the database exists and trusts the application before the application starts looking for it.
Learn more
- Provisioning a Database
- Database & Access samples
- Database manifest reference
- Application manifest reference
5. Let your customers sign in too
The win: Real customer accounts without hand-rolling a single session.
| Who this is for | A builder whose app now has users, not just visitors |
| Difficulty | Intermediate |
| Time | ~30–45 minutes |
| What you need first | Story 4 done, and a decision about which sign-in methods matter — email and password, Google, Apple, and so on |
| What it costs | Scales with sign-ins rather than running all the time; the estimate appears on the change before you approve it. |
In plain words
Story 3 solved a narrower problem: keeping Sam's own /admin page private. Now that there is a
real backend and a database, actual customers need accounts — sign up, log in, and stay logged in
between visits. This is a customer identity provider, not an internal gate restricted to Sam's own
account: it issues real end-user identities through whichever sign-in methods Sam picks.
Infrastream wires that provider into the same graph as the API and the database, so every request
the frontend makes carries a verified user the backend can trust. The app knows who is asking, and
Sam wrote none of it.
What you actually write
An IdentityProvider attached to the front door from Story 2. The providers block is the list of
buttons your customers will see on the sign-in page.
# .../project/samsapp-prod/public-ingress/customer-login.yaml
apiVersion: lowops.manifests.v1
kind: IdentityProvider
metadata:
name: customer-login
public-ingress: front-door
project: samsapp-prod
environment: production
organizational-unit: web
organization: samsapp
spec:
description: "Customer accounts for the web app."
displayName: "Sign in to Samsapp" # shown on the hosted login page
logoUrl: "img/logo.svg"
iconUrl: "img/icon.svg"
mode: REDIRECT # REDIRECT | POPUP
providers:
password: true # email and password
google: true # Google sign-in
Declaring the provider builds the login experience, but nothing is actually gated behind it until a
route says so — an HttpRoute rule with type: IDENTITY_PROVIDER and this provider in its
tenants list is what puts requests through it.
What the platform handles for you
- The hosted sign-in page, branded with your display name, logo and colours, so there is no login screen to build.
- The federated providers themselves, so adding Apple or Microsoft later is one more line rather than an OAuth integration project.
- Token verification at the edge, so your API receives requests that already carry a verified user instead of validating sessions itself.
- Signup rules — such as an email-domain allowlist or blocklist — so who may create an account is declared rather than policed after the fact.
Learn more
6. Let AI run your database
The win: Schema changes you read over coffee, instead of typing them into a production shell at 11pm.
| Who this is for | A builder who needs to change the database and would rather not do it by hand |
| Difficulty | Intermediate |
| Time | ~20 minutes to wire up, then ongoing |
| What you need first | Story 4 done, and pull-request access granted to the pvot agent on your repository |
| What it costs | Nothing extra for the migration itself — it runs once per deployment and stops. The database it changes is the ongoing cost, and it is a good candidate for the off-hours schedule — see Controlling Costs. |
In plain words
Sam's schema needs a new column, which normally means connecting to a production machine at 11pm, running a migration by hand, and hoping nothing breaks. Instead, Sam describes the change in plain language and pvot — the platform's AI agent — drafts it as a pull request: the actual SQL, the rollback plan, and the difference against the current schema. Sam reads it over coffee before merging. The database changes exactly when Sam says yes, never before, and never because somebody typed into a production shell.
What you actually write
Add two lines to the application — a job named migrate with type: BEFORE, which blocks the
deployment until it succeeds — and one JobConfig per environment describing how the migration
runs.
# .../project/samsapp-prod/job-config/waitlist-api-migrate.yaml
apiVersion: lowops.manifests.v1
kind: JobConfig
metadata:
name: waitlist-api-migrate # <application-name>-<job-name>
project: samsapp-prod
environment: production
organizational-unit: web
organization: samsapp
spec:
version: "1.4.0" # the image tag the migration runs from
container:
command: ["/migrate.sh"] # the entrypoint that applies your migrations
env:
- name: MIGRATION_MODE
value: "up"
The job reuses your application's own container image, so the migration always runs the version of your tooling that matches the code being deployed.
What the platform handles for you
- The one-off job and its ordering, so the main service is only deployed after the schema change succeeds — a failed migration stops the release instead of half-applying it.
- The same private path and credentials the application uses, so the migration never needs a separate admin password.
- The review gate, so an agent-authored change is still a diff a human approved — the agent proposes, it does not apply.
- A history of every schema change as commits, so rolling one back is the same action as rolling back code.
Learn more
7. Meet your users where they already are
The win: A second front door, not a second backend.
| Who this is for | A builder whose users would rather send a message than open a browser |
| Difficulty | Intermediate |
| Time | ~30 minutes |
| What you need first | Story 4 done, and a WhatsApp Business API or Meta developer account with a number you control |
| What it costs | One more small component that scales with message volume; the estimate appears on the change before you approve it. |
In plain words
Half of Sam's waitlist signups come from a region where nobody opens a browser to check an order status — they text. Adding WhatsApp as a channel does not mean standing up a second backend: the piece that receives messages is wired to the exact same API and the same database the website already uses. A user sends a message, the same business logic answers, and Sam's app quietly has two front doors that behave identically because underneath they are the same app.
The WhatsApp side of the arrangement — registering the number and the webhook with Meta — happens in the Meta developer console, not in a manifest. What Infrastream declares is the service that receives those messages and the credential it uses to reply.
What you actually write
A second application in the same set, holding the WhatsApp token as a secret and reading the same logical database as the website.
# .../application-set/samsapp/application/whatsapp-channel.yaml
apiVersion: lowops.manifests.v1
kind: Application
metadata:
name: whatsapp-channel
application-set: samsapp
release-track: main-track
organizational-unit: web
organization: samsapp
spec:
description: "Webhook that turns WhatsApp messages into API calls."
buildDefinition: whatsapp-channel # the BuildDefinition that builds this repository
target: CLOUD_RUN
project: samsapp-prod
accessControl:
secrets:
whatsapp-token: # a Secret manifest; the value lives in Secret Manager
envVar: WHATSAPP_TOKEN
database:
name: accounts-db # the same database the website uses
schema: accounts
privileges: [USAGE] # no CREATE: this service cannot alter the schema
secretSource:
envVar: DATABASE_URL
What the platform handles for you
- A separate service identity for the channel, so what the chat front door may do is declared independently of what the website may do.
- The WhatsApp token as a managed secret, injected at runtime and never committed, so rotating it does not mean editing code.
- The same private database path as the API, so the second channel cannot drift onto a different copy of your data.
- The public route and certificate for the webhook address, so Meta has a stable HTTPS endpoint to deliver messages to.
Learn more
8. Give your WhatsApp bot the same identity as your app
The win: One customer across every channel — and the bot can only do what its own leash allows.
| Who this is for | A builder running more than one channel who does not want two copies of each customer |
| Difficulty | Intermediate |
| Time | ~30–45 minutes |
| What you need first | Story 5 done (customer identity) and the WhatsApp channel from Story 7 in place |
| What it costs | Nothing extra to run; phone verification is charged per message sent, so the region allowlist below is a cost control as well as a fraud control. |
In plain words
A customer messaging the WhatsApp number and that same customer logged into the website should resolve to one account, not two silos that happen to share a database. When a phone number is verified, it belongs to the same customer identity from Story 5, so order history and preferences follow the person rather than the channel. The bot answering those messages, though, is not the customer: it needs its own identity, scoped to only the actions it is allowed to take on someone's behalf and kept separate from that person's own credentials. Who the customer is, and what the bot may do for them, are two different declarations.
What you actually write
Extend the Story 5 provider so the same tenant also accepts a verified phone number. Phone sign-in requires an SMS region policy — the platform refuses the configuration without one.
# .../project/samsapp-prod/public-ingress/customer-login.yaml (Story 5, extended)
apiVersion: lowops.manifests.v1
kind: IdentityProvider
metadata:
name: customer-login
public-ingress: front-door
project: samsapp-prod
environment: production
organizational-unit: web
organization: samsapp
spec:
displayName: "Sign in to Samsapp"
logoUrl: "img/logo.svg"
iconUrl: "img/icon.svg"
providers:
password: true
google: true
phone: true # the same tenant now accepts a verified phone number
smsRegionPolicy: # required whenever phone sign-in is enabled
allowedRegions: ["BR", "IN", "NG"]
The bot's own leash is the accessControl block on the channel application from Story 7: it lists
exactly which database, which privileges and which secrets that service may use, and nothing else.
Linking a phone number onto an existing email account is behaviour of the identity platform rather than a manifest field. Ask the pvot agent to draft this one — see Using pvot.
What the platform handles for you
- One identity tenant across every channel, so a person who arrives by chat and a person who arrives by browser are the same account.
- The SMS region allowlist, so verification messages can only be sent to regions you named — the standard defence against toll fraud.
- A distinct service identity for the bot, so "what the bot may do" is granted to the bot and never borrowed from the customer.
- Custom claims on the user's token, so your API can act on facts about the user that the platform attached at sign-in rather than facts your code guessed.
Learn more
9. Ship it to mobile too
The win: The third surface costs an hour, not a third authentication system.
| Who this is for | A builder adding a mobile app to a product that already has a web and a chat surface |
| Difficulty | Intermediate |
| Time | ~1 hour |
| What you need first | Story 5 done (customer identity) and a mobile app project, new or existing |
| What it costs | Nothing extra on the platform side — the mobile app runs on your users' devices and calls the API and identity provider you already run. |
In plain words
By now Sam's app has a website, a WhatsApp bot and real customer accounts. The last ask is a mobile app, and the instinct is to dread it: a third login flow, and a third set of API contracts to keep in sync with the other two. Instead, the mobile app signs users in against the same customer identity from Story 5 and calls the same API from Story 4. Three surfaces — web, chat and mobile — share one identity, one backend, and one place where changes are made.
What you actually write
Nothing new for the mobile app itself. What matters is that the API route names the same identity tenant the website uses, so a token issued to a phone is accepted exactly like a token issued to a browser.
# .../public-ingress/http-route/api-routes.yaml
apiVersion: lowops.manifests.v1
kind: HttpRoute
metadata:
name: api-routes
public-ingress: front-door
project: samsapp-prod
environment: production
organizational-unit: web
organization: samsapp
spec:
description: "The API every surface calls — web, chat and mobile."
rules:
- matches:
- prefixMatch: /api
authentication:
- type: IDENTITY_PROVIDER
tenants: [customer-login] # the same tenant the website signs into
action:
destinations:
- deploymentConfig: waitlist-api
port: 8080
If the mobile project is also built by the platform, it gets a BuildDefinition of its own like any
other repository — the build toolchain is selected by the definition's type.
What the platform handles for you
- One identity tenant serving all three surfaces, so there is no second user table to reconcile later.
- Token verification at the edge for every surface, so the mobile client gets the same guarantees as the browser without extra API code.
- One API and one database behind all of it, so a change to your business logic reaches every surface at once instead of three times.
- The same review-and-price flow for any new surface you add, so surface number four is the same kind of pull request as this one.
Learn more
Where to go next
If the project outgrows one person — a second engineer, a staging environment, requests for small infrastructure changes landing in your messages — continue with the Growing Team arc, stories 10 to 14, which picks up exactly where Sam leaves off here. If you would rather start building now, the Getting Started guide walks through your first repository end to end, and the Manifest Reference documents every field used on this page.