Areas we serve
Chapter 8

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.

SymptomLayerWhat 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 scriptFrontendThe 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 disagreeDatabaseThe 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 refreshHost / frontendThe 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 elseHostNothing 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 loginHost / frontendNo 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 serverBackend configAPP_KEY or NEXTAUTH_SECRET changed, or the cookie domain does not match the public site name.
“Table does not exist”DatabaseThe schema was created on the laptop by a migration that nobody ran in production.
Secrets the model will casually invent

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

  • .env is listed in .gitignore, and .env.example has 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
  1. Backend and database — the seat check was not enforced in one transaction.
  2. Frontend config — the public API URL is still the development default.
  3. Host / config — the environment was not set.
  4. Host — HTTPS is not finished. See Book 2.
  5. Database — state lived on disposable container disk. Move it to a database service with a volume or a managed Postgres, and take backups.