What Breaks in Production
Env files, secrets, SPA routes, SQLite on the wrong disk, and HTTPS.
Learning objectives
- Diagnose the failures that appear the day an app leaves a laptop
- Keep secrets out of the frontend and out of git
- Give each failure a layer so the fix is obvious
The app worked yesterday
Generated apps fail in production in a small set of ways. The code did not “randomly break.” A setting that was implicit on your laptop is missing, public, or pointed at the wrong place.
| Symptom | Layer | What happened |
|---|---|---|
| Works locally, crashes on boot with “environment variable missing” | Host / config | .env was never deployed. Example files are documentation, not configuration. Set the variables in the host, then restart the process. |
| Database password visible in the browser’s script | Frontend | The tool put a secret in a NEXT_PUBLIC_ or other client-side variable, or pasted a key into a component. Anything shipped to the browser is public. Rotate the key and move it server-side. |
| Bookings vanish after a restart, or two copies of the app disagree | Database | The SQLite file lived inside a container layer or a folder that deploy replaces, or two app instances each opened their own file. Keep one studio.db on a persistent disk and one writer. Move to Postgres when you truly need a second writer, not because SQLite is “not a real database.” |
The home page loads, but /classes/thursday is a 404 after refresh | Host / frontend | The single-page app handles that URL in JavaScript. The server looked for a file and missed. Unknown paths have to fall back to index.html. |
| The site loads on port 3000 and nowhere else | Host | Nothing is listening on 80 and 443. A web server or the platform’s proxy has to forward public HTTPS to the app port. |
| The browser says “not secure,” or the page is blank after login | Host / frontend | No certificate, or the app still redirects to http:// and to localhost. Set the public URL and finish HTTPS. Book 2 covers certificates. |
| Login works locally and rejects everyone on the server | Backend config | APP_KEY or NEXTAUTH_SECRET changed, or the cookie domain does not match the public site name. |
| “Table does not exist” | Database | The schema was created on the laptop by a migration that nobody ran in production. |
If a chat transcript, a screenshot, or a commit contains a live database URL or API key, treat it as public. Generate a new password, update the host, and do not “fix” it by editing history and hoping. Book 8’s chapter on authentication and API keys is the longer version of this rule.
A ten-minute pre-flight
.envis listed in.gitignore, and.env.examplehas blanks rather than real passwords.- Searching the frontend for the database password returns nothing.
- The production database is either a server, or one SQLite file on a disk deploy does not wipe, with a copy you have restored once.
- Migrations have been run against that database.
- The public site URL in the environment matches the domain you will actually use, including
https://.
Name the layer
Five reports come in the morning after launch. Name the layer for each: (1) two visitors booked the same last seat, (2) the homepage loads but every API call goes to localhost, (3) the app cannot connect because DATABASE_URL is empty, (4) the certificate warning, (5) yesterday’s bookings are gone after the host rebuilt the container and the only database was a file inside it.
Answer
- Backend and database — the seat check was not enforced in one transaction.
- Frontend config — the public API URL is still the development default.
- Host / config — the environment was not set.
- Host — HTTPS is not finished. See Book 2.
- Database — state lived on disposable container disk. Move it to a database service with a volume or a managed Postgres, and take backups.