Draft, not yet published
Deploying a Windsurf project to production: what changes
7 July 2026· 5 min read · by Stackbastion
Windsurf built you a working app and it runs great on localhost. Now you want it on the internet with a real domain and real users, and you’re realizing “it works locally” and “it’s deployed to production” are two very different milestones. Here’s what actually changes between them, and how to close the gap.
Why this happens with Windsurf
Windsurf is an AI coding environment, and like every AI coding tool, it optimizes for getting a working app in front of you fast. The default target is your machine: localhost, a dev server, a local database, secrets sitting in a .env file, and no thought given to what happens when a stranger hits the app at the same time as fifty other strangers.
That’s the right default for building. It’s the wrong assumption for shipping. Production introduces things localhost never tests: a real domain with HTTPS, environment variables that have to exist on a server you don’t own, a database that survives restarts and gets backed up, concurrent traffic, and the reality that anything reachable on the internet gets probed by bots within hours. Windsurf writes correct code for the local case and stays silent on all of it, because none of it is in the file it’s editing.
So nothing about your Windsurf project is wrong. It’s just built for a world with one user on a trusted network, and production is neither.
What changes, step by step
Here’s the concrete list of what’s different in production and what to do about each.
Secrets move out of your code.
Locally, a .env file is fine. In production, those values live in your host’s secrets manager or environment settings, never committed to the repo. Before you deploy, make sure nothing secret is in the bundle the browser downloads:
# Confirm no secrets shipped in the built frontend
grep -rEo "(sk_live_|service_role|AIza|AKIA)[A-Za-z0-9_-]+" ./dist
Empty output is what you want. Anything else has to move server-side before you go live.
The database becomes real infrastructure.
Localhost probably used a local Postgres or SQLite file that resets when you feel like it. Production needs a database that persists, handles concurrent connections, and is backed up. The connection string changes, and critically, you want connection pooling in front of it so a burst of traffic doesn’t exhaust the connection limit. If you’re running your own, a minimal Postgres in Docker:
services:
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./pgdata:/var/lib/postgresql/data
The restart: unless-stopped line matters: production databases have to come back after a reboot without you touching them.
HTTPS and a domain get added.
Localhost is plain HTTP and nobody cares. Production needs a domain and a valid TLS certificate, and modern features (service workers, secure cookies, some APIs) simply won’t run without HTTPS. A reverse proxy like Caddy handles certificates automatically:
your-app.example.com {
reverse_proxy localhost:3000
}
That four-line config gets you automatic HTTPS with certificate renewal handled for you. Point your domain’s DNS at the server and Caddy does the rest.
Failure paths start mattering.
On localhost you never see the case where the database is momentarily unreachable or a third-party API times out, because nothing’s under load. In production those happen. Every external call needs a failure path, and the app should degrade to something sensible instead of a white screen.
You add eyes: monitoring and logs.
Locally, you’re watching the terminal. In production, you’re asleep when it breaks. Uptime monitoring tells you an endpoint went down before your users do, and searchable logs are the difference between a ten-minute fix and a two-hour guess at 2am. Set both up before launch, not after your first outage.
Backups become non-negotiable.
There’s no undo button in production. A backup you’ve actually restored at least once is the safety net, and the restore test matters more than the backup itself. Our backup and restore tutorial walks through point-in-time recovery, which protects you against the mistake you don’t notice for a week.
The pre-launch checklist
- No secrets in the frontend bundle or committed to the repo
- Database persists across restarts and has connection pooling
- Domain configured with automatic HTTPS
- Every external call has a failure path
- Uptime monitoring and searchable logs in place
- Backups configured, and one restore actually tested
- A quick concurrency test done before real traffic arrives
Or, we do it for you
Stackbastion supports Lovable and Git-repo imports directly today, and a Windsurf project is standard code the same production principles apply to. If you want a second pair of eyes on your setup regardless of what you built it with, get a free production audit.
FAQ
Can I just deploy straight from Windsurf and be done?
You can get it online, but online and production-ready aren’t the same. A one-click deploy handles hosting the code; it doesn’t set up backups, connection pooling, monitoring, or move your secrets somewhere safe. Those are the things that decide whether launch day is calm or an incident, and they’re on you to add.
Do I need my own server, or is a platform host fine?
Either works, and a platform host is the simpler start. The checklist is the same regardless: secrets managed properly, a real backed-up database, HTTPS, monitoring, and failure handling. The difference is who runs the infrastructure. A platform does more for you at higher cost; your own server is cheaper but makes you the operations team.
What breaks most often going from localhost to production?
Two things. Environment variables that existed locally but were never set on the server, so the app boots into a half-broken state. And database connections, because code that opens a connection per request is invisible on localhost and falls over the moment real concurrent traffic arrives. Check both before you announce.