This is the checklist we work through on a Production Check, lightly edited to remove the parts that only make sense with a repository open in front of us. It is deliberately long. You do not need every item before launch - you need to have made a decision about every item, and to know which ones you are consciously skipping.
Read it with a text file open and write down two lists: done, and deliberately deferred. The second list is the valuable one. An app with fourteen known gaps and a plan is in far better shape than an app with four unknown ones.
1. Secrets and configuration
Secrets are the fastest path from a small problem to an unrecoverable one, so they go first.
- No secret carries a client-exposed prefix (NEXT_PUBLIC_, VITE_, REACT_APP_, PUBLIC_). Verified by grepping the built bundle, not by reading the source.
- The built bundle contains no JWTs, no sk_ keys, no AKIA identifiers, no private key blocks. Checked against production assets, not a local build.
- Git history is clean. Scanned with gitleaks or trufflehog, not by eye. Push protection enabled going forward.
- Every environment has its own credentials. A development key cannot touch production data.
- Secrets live in a managed store - platform environment variables, Doppler, AWS Secrets Manager - and not in a .env file on someone's laptop.
- There is a written rotation procedure, and someone other than the founder can execute it.
- Third-party keys are scoped down: Stripe restricted keys, least-privilege IAM, read-only tokens where reads are all that is needed.
2. Authentication and authorization
Authentication is usually fine - it comes from a library. Authorization is usually not, because it is the part the AI has to infer from your product, and it infers permissively.
- Every table has row-level security enabled, or the equivalent enforced in a server layer no client can bypass.
- No policy uses USING (true), and no policy relies solely on the user being authenticated.
- Authorization is enforced at the data layer, not only by hiding buttons in the UI. Test by calling the API directly with a second user's session.
- IDs in URLs are checked for ownership on every request. Change the id in the address bar to another tenant's and confirm you get a 403, not a page.
- Role and permission data lives in a table users cannot write to.
- Password reset tokens are single-use, time-limited, and invalidated on use. Reset does not leak whether an email exists.
- Logout invalidates the session server-side. Clearing localStorage is not logout.
- Auth endpoints - login, signup, reset, magic link, OTP - are rate limited per IP and per account.
- Admin surfaces require a distinct check, not just a truthy flag on the user object.
- Session cookies are HttpOnly, Secure, and SameSite=Lax or stricter.
3. The data layer
Generated schemas are optimised for getting something working. Constraints, indexes, and migration hygiene are what make them survive growth.
- Foreign keys exist and are enforced. Orphaned rows are a data-integrity bug that compounds silently.
- NOT NULL, CHECK, and UNIQUE constraints reflect the rules your application actually assumes.
- Every column used in a WHERE, JOIN, or ORDER BY on a hot path has an index. Confirmed with EXPLAIN ANALYZE, not by intuition.
- No unbounded queries. Every list endpoint paginates with a hard maximum page size.
- Money is stored as integer minor units or NUMERIC. Never as a float.
- Timestamps are timestamptz, stored in UTC, formatted at the edge.
- Schema changes are migration files in git, applied by the pipeline. Nobody edits production schema in a dashboard.
- Migrations are reversible, or the forward-only ones are marked and understood.
- Soft deletes and hard deletes are a deliberate choice per table, and the choice is documented.
4. Backups and recovery
The only backup that counts is one you have restored. Everything else is a hopeful configuration setting.
- Automated backups are enabled, with a retention window matched to how long it would take you to notice a problem.
- Point-in-time recovery is on for anything holding customer data.
- You have performed a real restore into a scratch environment, timed it, and written down the number. That number is your actual RTO.
- Recovery point objective is a decision, not an accident. 'We can lose up to fifteen minutes' is fine, if it is a sentence someone said out loud.
- Backups are stored outside the primary account or project, so an account compromise or a billing lapse does not take them with it.
- Uploaded files are backed up too. Object storage is durable, but durable is not the same as protected against your own delete.
- There is a written runbook for the two most likely disasters: accidental mass delete, and total environment loss.
5. The runtime
- A production application server, not a framework development server. Multiple workers, explicit timeouts, graceful shutdown.
- Debug mode is off. Stack traces are logged, never returned to the client.
- The container filesystem is treated as disposable. No SQLite, no user uploads, no session files on local disk.
- Health and readiness endpoints exist and are wired into the platform's checks.
- Resource limits are set, and the app has been observed under load to confirm it does not get OOM-killed at p99.
- TLS everywhere, HTTP redirected to HTTPS, HSTS considered and consciously enabled or deferred.
- Security headers are present: Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, and a restrictive Permissions-Policy.
- CORS lists explicit origins. A wildcard on a credentialed API is a finding, not a convenience.
- Dependencies are pinned with a lockfile, and advisories are monitored automatically.
6. Observability
You cannot operate what you cannot see. The bar is low and the payoff is immediate: most teams go from finding out about outages via customer email to finding out via a phone alert in about two hours of setup.
- Error tracking with source maps uploaded, so a stack trace points at real code.
- Uptime monitoring from outside your own infrastructure, checking a route that exercises the database rather than a static page.
- Structured JSON logs with a request id threaded through every entry, so one user's failing request can be reconstructed end to end.
- Logs are searchable and retained long enough to investigate something reported a week late.
- Alerts route to a human - phone, Slack, PagerDuty - with thresholds tuned so they are believed. An alert nobody trusts is worse than no alert.
- A small dashboard covering request rate, error rate, p95 latency, and database connections. Four numbers on one screen.
- Slow query logging is enabled and reviewed, not just enabled.
- Personal data is scrubbed from logs and error payloads before it leaves your infrastructure.
7. Delivery pipeline
- Production deploys from a git branch. Nobody ships by clicking Publish in a builder.
- CI runs on every pull request: typecheck, lint, tests, build.
- A staging environment exists with its own database and its own credentials.
- Rollback is one command or one button, and someone has actually used it.
- Database migrations run as an explicit, ordered step, with a plan for the ones that cannot run while the old version is live.
- Preview deployments per pull request, so review happens against something running.
- Tests cover the invariants that would cost money if broken: authorization, payment, and data-integrity paths. Coverage percentage is not the target; those three areas are.
8. Frontend and performance
- Largest Contentful Paint under 2.5s on a mid-tier phone over 4G, measured with real throttling rather than a desktop Lighthouse run.
- Bundle size is known and budgeted. Anything over about 300KB gzipped for the initial route needs a reason.
- Images are sized, compressed, modern-format, and lazy-loaded below the fold.
- No layout shift on load. Reserve space for images, embeds, and fonts.
- The app renders something useful without JavaScript executing, or degrades explicitly with a clear message.
- Loading, empty, and error states exist for every asynchronous surface. Spinners that never resolve are a support ticket generator.
- Keyboard navigation works, focus is visible, and forms have labels. Accessibility is also the cheapest usability testing available.
9. Privacy and legal basics
Not legal advice, but these are the items that come up in every enterprise procurement conversation, and having them ready is the difference between a two-week security review and a two-month one.
- You can enumerate what personal data you store, where it lives, and why.
- A privacy policy and terms of service exist and describe what the app actually does.
- Data deletion works end to end: account deletion removes or anonymises data in the database, object storage, analytics, and email tooling.
- Data export exists if you have European or Californian users.
- Subprocessors are listed. Every API you call with user data is a subprocessor.
- Analytics and session-replay tools are configured to mask sensitive fields, and cookie consent matches your actual jurisdiction.
- You know which regions your data is stored in, because someone will eventually ask.
10. Cost
Cost is a production concern. An app that becomes unaffordable at 10x traffic has a scaling problem, even if it never falls over.
- You have modelled the bill at 10x and 100x current traffic, per service.
- Billing alerts are configured on every provider, at a threshold you would want to hear about.
- Metered upstreams - LLM calls, email, SMS, image processing - have per-user rate limits so one abusive account cannot generate a five-figure invoice.
- Egress and storage growth are understood. Egress is the line item that surprises people.
- You know the unit economics: cost per active user per month, and how it changes with usage.
How to use this without stalling
The point is not to complete the list before launching. Plenty of good businesses launched with half of it undone. The point is that the undone half should be chosen rather than discovered. Work top to bottom - the ordering above is roughly by cost of getting it wrong - and stop when the remaining items are ones you would be comfortable explaining to a customer whose data you just lost.
If you would rather not run it yourself, that is what our Production Check is: this production readiness checklist executed against your codebase by an engineer, with findings ranked by severity and an estimate against each one. The report is yours whether or not you hire us for the remediation.
Written by Kato Chojiro
Takes AI-built MVPs to production: security, data, and the infrastructure underneath.
Connect on LinkedIn