You prompted your way to a working app. Maybe you used Cursor, maybe Claude Code, maybe bolt.new or Lovable or v0. You spent a weekend going back and forth with an AI, and now you have something real. It runs on localhost. It looks good. You showed it to a friend and they said "ship it."

So how do you actually ship it?

The gap between "it works on my machine" and "it's live on the internet, handling real users, and not leaking API keys" is where most vibe-coded projects die. Not because the code is bad, but because deployment and security weren't part of the prompting session. The AI built you a beautiful frontend and a functional backend. It did not configure HTTPS, set up environment variables, or think about what happens when a stranger sends malformed JSON to your API.

This guide covers the deployment and security fundamentals that every vibe-coded app needs before it goes live. We'll go through hosting options, environment variable setup, security scanning with Snyk and Semgrep, and the specific vulnerabilities that AI-generated code tends to introduce. Real commands, tested configs, no hand-waving.

01 Why Vibe-Coded Apps Need Extra Attention

AI coding tools are trained on public repositories. Public repositories contain brilliant code and terrible code in roughly equal measure. The AI doesn't distinguish between a secure pattern and a dangerous one. It just knows what patterns appear frequently.

After reviewing hundreds of AI-generated codebases, the same problems show up repeatedly:

Hardcoded secrets. AI tools love putting API keys directly in the source. They'll generate const OPENAI_API_KEY = "sk-proj-abc123..." without hesitation. That key ends up on GitHub. Bots scrape GitHub for exactly this pattern. Your key gets stolen within minutes.

No environment variables. The app has zero concept of configuration that changes between environments. Database URLs, API keys, domain names: all baked into the source code.

Authentication as an afterthought. The AI built a beautiful UI, but the API endpoints behind it are wide open. Anyone can hit them with curl. No login required.

Debug mode left on. Stack traces in error responses, development servers running in production, verbose logging that spills internal state to anyone who triggers an error.

No input validation. The AI assumed every request would look like what the frontend sends. Attackers don't use your frontend.

None of this is the AI's fault, exactly. These tools optimize for "get something working." They're not thinking about production. That's your job now.

The vibe coding trend is real. Product Hunt launches built entirely with AI tools are becoming common. Upwork demand for "Cursor developer" and "bolt.new" is climbing. But the projects that survive past the demo stage are the ones that handle deployment and security properly.

02 Choosing Where to Host

There's no single best hosting platform. The right choice depends on what you built.

Vercel

Best for Next.js apps, static sites, and frontend-heavy projects. Connect your GitHub repo, push to main, and Vercel handles the rest. Free tier includes serverless functions, edge functions, and automatic HTTPS.

# Deploy to Vercel
npm i -g vercel
vercel --prod

Use Vercel when your app is mostly frontend with some API routes. Don't use it if you need WebSockets, long-running processes, or a persistent server.

Railway

Best for full-stack apps that need a database. Railway gives you a container plus one-click Postgres, MySQL, or Redis. It reads your Dockerfile or detects your framework automatically.

# Deploy to Railway
npm i -g @railway/cli
railway login
railway init
railway up

# Add a database
railway add -p postgres

Fly.io

Best for Docker containers and apps that need to run in multiple regions. Good for WebSocket apps and anything you want deployed globally.

# Deploy to Fly.io
curl -L https://fly.io/install.sh | sh
fly launch
fly deploy

VPS (Hetzner, DigitalOcean)

Full control, cheapest long-term, steepest learning curve. Hetzner starts at about $4/month. You SSH in, install Docker, and manage everything yourself.

# On your VPS
apt update && apt upgrade -y
curl -fsSL https://get.docker.com | sh
git clone https://github.com/you/your-app.git
cd your-app
docker compose up -d

If you've never administered a server before, start with Vercel or Railway. You can always migrate to a VPS later when you understand what's happening under the hood.

03 Environment Variables: The Most Important Section

This is the single most critical thing to get right. Never put secrets in your code.

Environment variables are key-value pairs that exist outside your source code. Your app reads them at runtime. Different environments (local, staging, production) use different values.

# These are environment variables
DATABASE_URL=postgresql://user:pass@host:5432/mydb
OPENAI_API_KEY=sk-proj-abc123
SESSION_SECRET=a-random-64-character-string
NODE_ENV=production

Using them in your code

// Node.js - Good
const apiKey = process.env.OPENAI_API_KEY;

// Node.js - Never do this
const apiKey = "sk-proj-abc123";
# Python - Good
import os
api_key = os.environ["OPENAI_API_KEY"]

# Python - Never do this
api_key = "sk-proj-abc123"

Local development with .env files

Create a .env file in your project root for local development. Add it to .gitignore immediately:

# .env (DO NOT COMMIT THIS FILE)
DATABASE_URL=postgresql://localhost:5432/myapp_dev
OPENAI_API_KEY=sk-proj-your-dev-key
SESSION_SECRET=local-dev-secret
# Add to .gitignore BEFORE your first commit
echo ".env" >> .gitignore

Load it in Node.js with dotenv:

npm install dotenv
// At the top of your entry file
require('dotenv').config();

Setting env vars on your host

# Vercel
vercel env add OPENAI_API_KEY

# Railway
railway variables set OPENAI_API_KEY=sk-proj-xxx

# Fly.io
fly secrets set OPENAI_API_KEY=sk-proj-xxx

Also create a .env.example file with dummy values and commit it. This documents what variables your app needs without exposing real credentials:

# .env.example - copy to .env and fill in real values
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
OPENAI_API_KEY=sk-proj-your-key-here
SESSION_SECRET=generate-a-random-string-here
NODE_ENV=development

04 SSL, Domains, and HTTPS

Every production app needs HTTPS. No exceptions. Without it, data between your users and your server travels in plain text. Anyone on the same network can read it.

If you're on Vercel, Railway, or Fly.io, SSL is automatic. Point your domain via a CNAME record and you're done.

On a VPS, the simplest approach is Caddy. It handles SSL certificates, renewal, and HTTPS redirects with a two-line config:

# Caddyfile
yourdomain.com {
    reverse_proxy localhost:3000
}

That's the entire config. Compare that to manually configuring Nginx with certbot. Caddy is the right default for solo developers running a single app.

If you prefer Nginx, use certbot:

apt install certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com -d www.yourdomain.com
certbot renew --dry-run

05 Security Scanning with Snyk

Your app has dependencies. Those dependencies have dependencies. Somewhere in that tree, there are packages with known vulnerabilities. You need a tool that checks the entire tree against a database of CVEs and tells you what to fix.

That tool is Snyk.

Setup

# Install Snyk CLI
npm install -g snyk

# Authenticate (opens browser)
snyk auth

The free tier gives you 200 tests per month. More than enough for a solo project.

Running a scan

snyk test

Example output:

Testing /home/user/my-vibe-app...

Tested 347 dependencies for known issues, found 3 issues.

  Upgrade [email protected] to [email protected]
  ✗ Open Redirect [Medium Severity]

  Upgrade [email protected] to [email protected]
  ✗ Improper Restriction of Security Token Assignment [High Severity]

  Upgrade [email protected] to [email protected]
  ✗ Server-Side Request Forgery [High Severity]

For most issues, the fix is just upgrading:

# Fix what Snyk found
npm update express jsonwebtoken axios

# Run again to confirm
snyk test

Continuous monitoring

# Take a snapshot for ongoing monitoring
snyk monitor

This uploads your dependency tree to Snyk's dashboard. When new vulnerabilities are discovered in packages you use, Snyk emails you. You can also connect your GitHub repo so Snyk automatically opens PRs to fix vulnerable dependencies.

06 Code Scanning with Semgrep

Snyk checks your dependencies. Semgrep checks your actual code. It scans for patterns like hardcoded secrets, SQL injection, XSS vulnerabilities, and hundreds of other issues that AI coding tools routinely generate.

Setup

# Install via pip
pip install semgrep

# Or via Homebrew
brew install semgrep

No account needed for local scanning.

Running a scan

semgrep scan --config auto

The auto config pulls in Semgrep's recommended ruleset, which covers the most common security issues across JavaScript, TypeScript, Python, Go, Java, and more.

Example output:

Scanning 42 files with 389 rules...

  src/api/users.js
    javascript.express.security.audit.xss.mustache-escape
      Detected potential XSS. User input flows into response
      without escaping.
      14│ res.send(`<h1>Welcome ${req.query.name}</h1>`);

  src/db/queries.js
    javascript.lang.security.audit.sqli.node-sql-injection
      SQL injection risk. Use parameterized queries instead of
      string concatenation.
      23│ const result = await db.query(
           `SELECT * FROM users WHERE id = ${userId}`);

  src/config.js
    generic.secrets.security.detected-openai-api-key
      OpenAI API key detected in source code.
       8│ const OPENAI_KEY = "sk-proj-abc123def456";

Rules that catch vibe coding mistakes

Semgrep's auto config includes rules specifically relevant to AI-generated code:

The scan-fix loop

Here's the workflow that catches 90% of security issues before they reach production:

# 1. Scan for code issues
semgrep scan --config auto

# 2. Fix everything flagged

# 3. Scan for dependency vulnerabilities
snyk test

# 4. Update vulnerable packages
npm update

# 5. Run both scans again to confirm zero issues
semgrep scan --config auto --error --severity ERROR
snyk test --severity-threshold=high

This loop takes five minutes. Build it into your routine before every deploy.

If you're using Claude Code or Cursor, you can hand the scan output directly to the AI and tell it to fix every finding. Then re-run to verify. The AI is good at fixing these issues when you point them out explicitly.

Want the full deployment + security workflow?

This post covers the fundamentals. The complete Vibe Coding Deployment Guide includes CI/CD pipeline configs (GitHub Actions for Node.js and Python), custom Semgrep rules for AI-generated code patterns, Docker production configs, monitoring setup with Sentry and UptimeRobot, and printable pre-deploy and post-deploy checklists.

Get the full guide — $34

07 Common Security Holes in AI-Generated Code

These are the vulnerabilities that show up most often in vibe-coded projects. Each one is fixable in under five minutes once you know what to look for.

Wide-open CORS

AI tools frequently generate app.use(cors()) with no configuration, which sets Access-Control-Allow-Origin: *. This means any website on the internet can make requests to your API on behalf of your logged-in users.

// Bad: allows every origin
app.use(cors());

// Good: restrict to your domain
app.use(cors({
  origin: ["https://yourdomain.com"],
  credentials: true,
}));

SQL injection

AI tools frequently produce queries with string interpolation instead of parameterized queries:

// Dangerous: AI-generated SQL
const user = await db.query(
  `SELECT * FROM users WHERE id = ${req.params.id}`
);

// Safe: parameterized query
const user = await db.query(
  "SELECT * FROM users WHERE id = $1",
  [req.params.id]
);

If you're using an ORM like Prisma or Drizzle, you're mostly safe. ORMs parameterize queries by default. But watch out for raw query methods like prisma.$queryRawUnsafe().

No rate limiting

Without rate limiting, an attacker can send thousands of requests per second. They can brute-force passwords, drain your OpenAI credits (if you're calling the API per request), or just crash your server.

const rateLimit = require("express-rate-limit");

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                  // 100 requests per window per IP
});

app.use("/api/", limiter);

// Tighter limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
});
app.use("/api/auth/login", authLimiter);

Missing input validation

Your frontend has form validation? Great. Attackers don't use your frontend. Every API endpoint needs server-side validation:

const { z } = require("zod");

const createUserSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().min(1).max(100),
  password: z.string().min(8).max(128),
});

app.post("/api/users", (req, res) => {
  const result = createUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error.issues });
  }
  createUser(result.data);
});

Also limit request body size to prevent payload-based denial of service:

app.use(express.json({ limit: "1mb" }));

Exposed debug endpoints and stack traces

Development error handlers leak your internal code structure. In production, return generic errors:

if (process.env.NODE_ENV === "production") {
  app.use((err, req, res, next) => {
    console.error(err);  // Log server-side
    res.status(500).json({ error: "Internal server error" });
  });
}

Disable source maps in production too:

// next.config.js
module.exports = {
  productionBrowserSourceMaps: false,
};

08 Pre-Deploy Checklist

Run through this before every production deploy:

What We Didn't Cover

This guide hits the areas that matter most for getting your first deploy right. But a complete production setup goes further. The full Vibe Coding Deployment Guide also covers:

Get the Complete Vibe Coding Deployment Guide

Everything above plus CI/CD configs, custom Semgrep rules, Docker production templates, monitoring setup, and printable checklists. Go from localhost to production with confidence.

Download the guide — $34

Start Shipping

The hardest part of vibe coding isn't building the app. It's getting it live without leaving the doors unlocked. Run the scans, fix what they find, set up your environment variables, pick a host, and deploy.

Your app deserves to be more than a localhost demo. Ship it for real.