How to deploy a vibe-coded app properly

Deploying a vibe-coded app properly means it builds and runs the same way every time, from code you control, with configuration and secrets supplied by the environment rather than written into the code. Put the code in your own Git repository, give development, staging and production their own databases and keys, let a pipeline test before it builds and deploys, and keep the previous version ready to restore. You can stay on the builder's hosting if it gives you all of that.

By Adapted from Chapter 13, Packaging & Deploying AI Systems, of From Model to Product ·

What deploying properly means

An AI app builder's publish button gives you a URL. That is a deployment, but not yet a deployment process. Chapter 13 of From Model to Product, on packaging and deploying AI systems, calls deployment the moment where engineering is tested, and starts from one question: how do you make sure the program runs the same way everywhere? Add a second, how you undo a bad release, and you have four properties to aim for. The rest of this guide takes them in order.

  • Reproducible: a fresh copy of the code on a new machine builds and runs the same app.
  • Configurable: whatever differs between environments, secrets included, comes from the environment rather than the code.
  • Tested: every release passes the same checks before it reaches users.
  • Reversible: the previous version can be restored in minutes.

Get the code into a repository you own

Start with ownership. Lovable, Bolt and Replit can all connect a project to a GitHub repository, and many AI app builders offer a repository sync or a code download. Lovable's sync, for example, is two-way and creates the repository in the account or organisation you choose (Lovable docs). Once the code is in your repository, every change has a history and you are free to deploy it elsewhere.

Then prove it builds without the builder. Clone the repository on a clean machine or in CI, install, build and run it. A builder hides steps that must become explicit: the runtime version, the build command, the environment variables, sometimes a database migration. Write each one in the README as you find it.

This is the chapter's reproducible environment applied to a JavaScript app. Its first step is a dependency file with exact versions; for Node that means committing the lockfile, installing with npm ci and pinning the Node version. Its next step is a container that defines the whole runtime, which deploying a model with FastAPI and Docker covers for a Python service.

Separate code, configuration and secrets

Chapter 13 separates three things: code defines the logic, the model defines the learned behaviour, and configuration defines how the system runs. Kept apart, each can change without rebuilding the others. In an app that calls a language model, the model name, the prompt templates and any thresholds sit on the configuration side, and they need versions like code, because changing any of them changes what users see.

Secrets are configuration with a rule of their own. The chapter is direct: secrets such as API keys should never be hardcoded inside the container image, but provided through environment configuration at runtime. The same applies to the code and the repository: keep a local .env file out of Git, commit a .env.example that lists every variable with no values, and set the real values in your host's environment settings. Then make the app refuse to start when one is missing, so a misconfigured release fails at deploy time instead of in front of a user. Values your framework bundles into browser code are public, never secret; securing a vibe-coded app covers which keys can live where.

// Read every setting once, at startup, and stop if any is missing.
const REQUIRED = ["APP_ENV", "DATABASE_URL", "LLM_API_KEY", "LLM_MODEL"] as const;

type Config = Record<(typeof REQUIRED)[number], string>;

export function loadConfig(env: Record<string, string | undefined>): Config {
  const missing = REQUIRED.filter((name) => !env[name]);
  if (missing.length > 0) {
    throw new Error(`Missing environment variables: ${missing.join(", ")}`);
  }
  return Object.fromEntries(REQUIRED.map((name) => [name, env[name]])) as Config;
}

export const config = loadConfig(process.env);
config.ts: import config instead of reading process.env across the code base. A missing variable stops the app at startup with a clear message.

Give development, staging and production their own everything

A builder usually gives you one environment: the one you edit is the one users see. That is fine while you are the only user. Once people depend on the app, you need at least three: development on your machine, staging that mirrors production, and production itself.

Separate means separate resources, not just separate URLs. Each environment gets its own database, its own sign-in configuration, its own model provider key with its own spending limit, and its own copy of the configuration. A test in staging then cannot delete real users' data, a leaked development key cannot spend production's budget, and a schema change can be tried before it reaches production.

Staging earns its keep when it matches production: same host, same build steps, same configuration names, test data. Where you can, promote the build that passed in staging rather than rebuilding it, so what you tested is what ships. One caution: frameworks fix browser-bundled variables at build time, so a build promoted between environments must not rely on them for anything that differs.

Who owns the database, the users and the keys?

Leaving a builder's hosting is mostly a question of what else the builder runs for you. Some, Lovable among them, provision a database, sign-in, file storage and server functions alongside the code, so the repository holds only part of the app. Lovable's documentation is explicit: the synced code includes the database migration files but not the data, and moving the PostgreSQL database alone does not move authentication, storage, realtime or Edge Functions (Lovable docs). It also notes that the built-in backend can stay where it is while the front end moves elsewhere.

That suggests the order: move one piece at a time, usually the front end first, and verify each copy before you retire the original. Whether you move or stay, answer these questions for each piece:

PieceAskWhy it matters
CodeIs it in a repository in your account, and does it build outside the builder?Without both, you cannot change hosts or hand the app to an engineer
DatabaseWhose account is it in, can you export schema and data, and are migrations in the repository?Data is the one part you cannot regenerate
User accountsWhere do users and their sign-in methods live, and can they move without everyone resetting a password?A failed move locks real users out
FilesWhere are uploads stored, and how are they exported?Storage often moves separately from the database
Server functionsWhere do they run, and which secrets do they use?They hold keys and much of the logic
Keys and billingWhich accounts hold the provider keys and the payment card?Keys on an account you do not control are not yours to rotate
DomainIs it registered to you, with DNS you can change?Moving hosts means pointing it somewhere new

A pipeline that tests before it builds

Chapter 13 lists what manual deployment gets wrong: the wrong version goes out, a dependency is missing, the server does not restart cleanly, a working system is replaced by a broken one. Its answer is a pipeline that takes the same road every time: run tests, build, store the build, deploy, and stop the release if any step fails.

Many hosting platforms build and deploy each push to a chosen branch, which covers the last steps. Add the first one yourself with a CI service such as GitHub Actions, and require its checks to pass before anything merges into the branch that deploys. The chapter's pre-build checks translate directly:

  • The app starts and its main routes respond.
  • Configuration can be read: every required variable is present.
  • The database connection works, and pending migrations apply cleanly to a copy.
  • The AI path returns a valid result: the model call stubbed in unit tests, and one real call in a smoke test against staging.
  • The prompt templates and model settings in the release are the versions you meant to ship.

Keep a way back

Every deployment should allow rollback, and the chapter sets four conditions for it. For risky changes such as a new model or prompt, release gradually as well; shadow, canary and A/B deployment compares the options. For an ordinary web app built with AI tools, the four conditions read like this:

  • Old builds are kept. Many hosts keep previous deployments and can restore one; find out whether yours does, and try it once before you need it.
  • Model and prompt versions are stored, not edited in place, so old code meets the prompts it was tested with.
  • Database changes stay compatible with the previous version: add columns and tables first, and remove old ones in a later release.
  • Configuration is versioned, so restoring a build also restores the settings it expects.

Should you leave the builder's hosting?

Not necessarily. Staying is reasonable while the hosting gives you a repository you own, separate environments, secrets held outside the code, logs you can read and a rollback you have tested. Bolt's help centre, for example, says that code synced to GitHub can be published with Bolt hosting, its Netlify integration or another service you choose, so the decision can wait until you have a reason.

The reasons are specific: a staging environment the builder cannot give you, background jobs or long-running work, control over where data is stored, costs that grow faster than usage, or engineers who need a standard workflow. The work above is what turns that move into a migration rather than a rewrite. Once the app is live, wherever it runs, monitoring a vibe-coded app covers how to tell whether it is still working.

A model that works on a laptop does not automatically work on a server. A backend that runs locally does not automatically handle multiple users. A system that works once does not automatically work continuously. A prototype that impresses a demo audience may collapse in production.

From Chapter 13 of From Model to Product

Questions

Can I keep hosting my app on the AI builder that made it?

Yes, if the hosting gives you what a product needs: code in a repository you own, separate staging and production, secrets kept out of the code, readable logs and a tested rollback. Some builders also let you host the synced code elsewhere while keeping their backend. Move when a specific need appears, such as background jobs, control over data location or a team workflow, not because self-hosting sounds more serious.

Do I need Docker to deploy a vibe-coded app?

Not always. Managed platforms build a typical JavaScript app straight from the repository, and a pinned runtime with a committed lockfile gives much of the reproducibility you need. A container earns its place when the app has a separate backend, system libraries, a Python model service or several services that must run together, because it defines the whole runtime rather than only the dependencies.

Where should API keys and environment variables live in production?

In your host's environment or secret settings, injected when the app runs, with a separate set for each environment. Never in the repository, the container image or any variable your framework bundles into browser code. Commit a .env.example that lists the names with no values, so anyone setting up a new environment knows what is required.

Do I need a staging environment for a small app?

Once real users depend on it, yes. A staging copy with its own database and keys is where you try a schema change, a new model or a new prompt without risking users' data or your production budget. It can be small and cheap, but it should be built and configured the same way as production, or it will not catch the problems that matter.

What should I do when a deploy breaks production?

Roll back first and investigate second. Restore the previous build from your host, check that its configuration and the database still match it, and confirm the app works before you debug. Then add a test to the pipeline that would have caught the failure, so the same release cannot reach users again.

How do I move my database off an AI app builder?

Export the schema and the data, restore them into a database in your own account, and verify the copy before you point anything at it. Check what else the builder ran alongside the database, such as user accounts, file storage and server functions, because those often move separately. Switch over at a quiet time, and keep the original until the new setup has run cleanly for a while.

About Dr. Dulan Dias