---
title: "How to deploy your vibe-coded app securely"
slug: deploy-your-vibe-coded-app-securely
severity: high
category: Secrets
summary: "Put server secrets in the host's environment, keep them out of the browser bundle, and do not give a preview deploy the production database or a public URL it does not need."
platforms:
  - Next.js
  - Vite
  - Railway
  - Vercel
  - Render
  - Fly.io
updated: "2026-10-14"
published: "2026-10-14"
related:
  - secret-keys-in-the-frontend
  - secrets-committed-to-git
  - exposed-env-and-debug-endpoints
---

A vibe-coded app usually deploys the same way it was built: the agent writes a `.env`, the host imports it, and a preview URL appears for every pull request. The security work is deciding which of those values the browser is allowed to see, which network path can reach the database, and which key a preview is allowed to hold.

Server-side environment variables are values the running server process can read. They are not automatically safe. Next.js inlines any name that starts with `NEXT_PUBLIC_` into the browser bundle at build time. Vite does the same for names that start with `VITE_`, through `import.meta.env`. A secret with either prefix is in the JavaScript you ship. The check for a key that already leaked is [Secret keys shipped in the frontend](/issues/secret-keys-in-the-frontend).

Private networking is how two of your own services talk without a public address. Preview hygiene is the rule that a pull-request deploy gets test credentials, not the production database password. Least privilege means the key you put in CI can deploy one environment, and the key in the app can only do the job that code does. A Stripe restricted key and a Supabase publishable key are that idea. A host account token that can see every project is not.

The four hosts below are in alphabetical order. One Railway link on this page is an affiliate link. That does not change this order, a comparison table, or a benchmark.

## How to check in 5 minutes

1. Search the repo for `NEXT_PUBLIC_` and `VITE_` next to `SECRET`, `SERVICE_ROLE`, `sk_live_`, `DATABASE_URL`, and `BEGIN PRIVATE KEY`. A secret must not use a public prefix.
2. Build the app and search the output JavaScript for those same strings. Dev mode can hide a value the production bundle still inlines.
3. In the host dashboard, open Production and Preview side by side. If the preview database URL or secret key is the production value, stop and split them.
4. List every service with a public URL. A database, a worker, or an internal API that only other services call should not have one.

## How to fix

### Next.js and Vite

Next.js documents that variables without `NEXT_PUBLIC_` are available in the Node.js environment and are not accessible in the browser. The `NEXT_PUBLIC_` prefix tells Next.js to inline the value into the client bundle at build time. After the build, changing that variable does not change the shipped JavaScript until you build again.

Vite documents the same boundary with a different prefix. A name starting with `VITE_` is exposed on `import.meta.env` and bundled into client code. Vite's env guide says those variables should not contain API keys. `DB_PASSWORD` without the prefix stays off the client. Lovable and Bolt apps are often Vite apps, so a `VITE_` secret is the same bug as a `NEXT_PUBLIC_` secret.

Read secrets from `process.env` in a Route Handler, a Server Action, or a separate backend. Do not pass them into a client component as props. Do not commit the file that holds the real values. Commit an example file with empty values, and keep `.env` and `.env.local` out of git. See [Keys committed to GitHub](/issues/secrets-committed-to-git).

### Fly.io, Railway, Render, and Vercel

**Fly.io.** Put credentials in `fly secrets`, not in the `[env]` table of `fly.toml`. Fly's configuration reference says `[env]` is for non-sensitive values, and that secrets take precedence over an env entry with the same name. Secrets are stored in an encrypted vault and injected as environment variables when a Machine boots. They are not in the image unless you deliberately use build secrets. Apps in the same organization share a private IPv6 network Fly calls 6PN. Another app is reached at `<app-name>.internal`. That network is the whole organization, not one environment: a staging app and a production app in the same organization can address each other. Put them in separate organizations if they should not. An app with no `services` or `http_service` section is not reachable from the public internet.

**Railway.** Variables on a service are available to the build and to the running deployment. A build command that prints the environment can write a secret into the build log, so do not echo them. Sealing a variable keeps it available to builds and deploys, but the dashboard and API cannot read it back. Railway does not copy sealed variables into pull-request environments, duplicated environments, or duplicated services. Give a PR environment its own test credentials. Services in the same project and environment reach each other at `<service>.railway.internal` over WireGuard, and a browser cannot open that name. The private network is isolated per environment, so production cannot reach staging over it. For CI, use a project token scoped to one environment in one project. An account token can see everything your user can see. Do not put an account token in shared CI. [Railway](https://railway.com?referralCode=nQXntZ). New users get $20 in Railway credits with our link. Railway's affiliate docs describe that credit as equivalent to a free month on the Pro tier.

**Render.** Set variables on the service's Environment page. If you describe the service in a Blueprint, mark a secret with `sync: false` so the value is entered in the dashboard and is not committed in `render.yaml`. Preview environments can override a variable with `previewValue`. That is the place for a test API key. Render's preview docs say placeholder variables with `sync: false` are not copied into preview environments. Services in the same region can use the private network and an internal hostname. Render documents blocking private traffic between environments on Pro workspaces and higher, not on every account. Prefer the database's internal connection string, and do not leave a public access list open when only other services need the database.

**Vercel.** Each variable can be limited to Production, Preview, Development, or a single preview branch. A branch-specific preview value overrides the shared preview value of the same name. Use that to point a branch at a preview database. Do not prefix a secret with `NEXT_PUBLIC_`. Preview deployments should not receive the production database URL or a live secret key. Private connectivity from Vercel Functions to a network you run is Secure Compute. Vercel documents Secure Compute as an Enterprise feature. It is not the default. A normal Vercel project does not put your function on a private network with your database. IP allowlisting still needs a password or a key. The address alone is not access control.

## Agent prompt

```text
You are checking how this app is deployed. Do not add features. Do not print secret values.

Search for NEXT_PUBLIC_ and VITE_ next to SECRET, SERVICE_ROLE, DATABASE_URL, sk_live_, and private keys. A secret must not use those prefixes.

Then tell me, without values:
1. Which env names are safe to be public, and which must stay on the server.
2. Whether a preview or pull-request environment is likely to receive the production database URL. If the repo cannot answer that, say so and name the host dashboard page I should open.
3. Which services should not have a public URL. For Fly.io, Railway, Render, or Vercel, name the private-network hostname pattern from the host's docs only if this repo already uses that host.

Do not add an affiliate link. Do not change comparison order or a benchmark.
```

## FAQ

### Can a preview deploy use the production database?

No. A preview URL is there so a change can be clicked before it is merged. Railway does not copy sealed variables into pull-request environments. Render can set a separate `previewValue`, and it does not copy `sync: false` secrets into previews unless you put them in a shared group on purpose. Vercel lets you scope a variable to Preview instead of Production. Use those mechanisms. A preview that holds the production database password is a second production.

### Do I have to change hosts to do this?

No. The checks are the same on Fly.io, Railway, Render, and Vercel. The host changes where you type the variable and whether private networking is on by default. Pick the host the app already deploys to. An affiliate link on this page does not change that.

## Sources

- [Next.js environment variables](https://nextjs.org/docs/app/guides/environment-variables)
- [Vite env variables and modes](https://vite.dev/guide/env-and-mode)
- [Fly.io secrets](https://docs.fly.io/apps/secrets)
- [Fly.io configuration](https://docs.fly.io/reference/configuration)
- [Fly.io private networking](https://docs.fly.io/networking/private-networking)
- [Railway variables](https://docs.railway.com/variables)
- [Railway environments](https://docs.railway.com/environments)
- [Lock down a production Railway project](https://docs.railway.com/guides/lock-down-production-project)
- [Railway private networking](https://docs.railway.com/private-networking)
- [Railway affiliate program](https://docs.railway.com/community/affiliate-program)
- [Render environment variables](https://render.com/docs/configure-environment-variables)
- [Render preview environments](https://render.com/docs/preview-environments)
- [Render private network](https://render.com/docs/private-network)
- [Vercel environment variables](https://vercel.com/docs/environment-variables)
- [Vercel Secure Compute](https://vercel.com/docs/secure-compute)
