Production Readiness Checklist for AI-Built Apps
AI-built apps need production readiness checks before launch. Security, backups, error monitoring, testing, and compliance prevent data loss and crashes.
- Check for hardcoded secrets and enforce authentication on every route before you push to production
- Test your backups by actually restoring them to a staging database--backups that have never been tested do not count
- Set up error monitoring (Sentry or similar) in 15 minutes and skip it at your peril; customers will find the bugs before you do
- Write integration tests on critical paths and authorization tests that explicitly verify users cannot access other people's data
- Configure rate limiting, database indexing, and pagination to handle real traffic; AI-generated code often assumes infinite resources
Your AI-built app runs on your laptop and passes tests locally. That does not mean it works for real users, survives traffic, handles data failures correctly, or blocks someone attacking it. This checklist walks you through eight categories that separate "shipped fast" from "shipped in flames" -- and tells you which ones to fix first.
Why Production Readiness Matters
AI tools optimize for features, not resilience. They handle the happy path. They get distracted by edge cases. Production readiness catches this gap before users find the problem.
Real consequences are concrete. Tea App leaked 72,000 photos because images were stored with predictable URLs and no access control. Lovable exposed 170+ databases because API keys were hardcoded. These were not theoretical risks--they were the live app burning down. The pattern is consistent: AI-generated code assumes your database never corrupts, your API endpoint never times out, users never input "' OR '1'='1", and traffic never spikes. Production is all of those things happening at once.
You have a working app. The problem is not ignorance--it is doubt. You want to know what could break, and what are the non-obvious fixes. This checklist answers that. It is organized by priority (must-fix before launch, should-fix before first customers, nice-to-have early). It is vendor-neutral. It names real tools you can use--Sentry, PostHog, Supabase, Docker--without gatekeeping. And it assumes you have the competence to ship. You just need the checklist.
Security & Credentials (Block Launch If Broken)
Every route needs authentication. Every public endpoint needs rate limiting. Every query parameter needs validation. Every credential needs to be outside your code. Check these four things first; everything else depends on them.
Hardcoded secrets are in your git history. Your API key for Stripe. Your database password. Your AWS credentials. An attacker clones your repo from GitHub and now has production access. Before you push to production: scan your repo for secrets using git-secrets, truffleHog, or grep for "password", "key", "token", "secret" in your code. Use environment variables instead. Use a secrets manager: Supabase manages this for you; if you self-host, use a.env file that is gitignored. Test that your code does not accidentally log a secret.
Authorization is not the same as authentication. Authentication: you are who you say you are. Authorization: you are allowed to do that. AI-generated code often gets this wrong. A user can view their own data because the middleware checks req.user.id === req.body.userId. But what if req.body.userId is someone else's ID? What if there is no check at all? Every route that modifies data needs an explicit authorization check. Not "the user is logged in"--"the user owns this resource" or "the user has admin role." Write tests that try to access resources you should not be able to. An endpoint that lists orders should only return your orders, not all orders.
Input validation stops SQL injection and XSS attacks. Validate every input. If you expect a number, check it is a number. If you expect an email, check it matches the email pattern. If you expect a short string, check its length. Reject anything that does not fit. Use a validation library (Zod, Yup, Joi). Do not rely on the database to reject bad data; do it in your code first. If a field is required, make sure it is not null or undefined. If a field has a max length, enforce it at the API level.
Rate limiting blocks bots and brute-force attacks. A bad actor tries 10,000 password guesses against your login endpoint. They try to scrape all your users. They DOS your API. Rate limiting says "you can make 10 requests per minute from this IP" or "you can make 100 requests per hour per user." Use a tool like Upstash (Redis as a service). If you use an API gateway (Cloudflare, nginx), configure rate limiting there. Test it: curl your API 100 times in a row. If it does not slow down or reject the requests, rate limiting is not working.
Security headers tell the browser not to trust things it should not. CORS restricts which domains can call your API. CSP restricts which scripts can run. HSTS forces HTTPS. Add these headers to every response: Access-Control-Allow-Origin: your-domain.com (not "*"); Content-Security-Policy: restrict scripts to your own domain; Strict-Transport-Security: enforce HTTPS. If you use an API framework (Express, FastAPI, Django), middleware is available for this. Test it by opening the browser console and checking that cross-origin requests fail if you did not allow them.
Data Integrity & Reliability
Backups are not backups until you test the restore. Database migrations can corrupt data if they are not run carefully. Cascading deletes can wipe the wrong table. Test all three before you trust them.
Backups need to be tested, not just enabled. You enabled "automatic daily backups" in your database settings. Good. But can you restore them? Have you actually restored a backup to a staging database and verified the data is correct? Most teams skip this. They enable backups and hope they never need them. Then the database gets corrupted and the backup is three weeks old, or it failed silently, or it cannot be restored because of a configuration issue. Before you launch: take a backup, restore it to a staging database, run a query to confirm the data is identical. Do this monthly, not just once.
Database migrations need a dry-run mode. You are changing your schema. You are adding a NOT NULL column. What happens to existing rows that do not have a value for that column? The migration fails. Or it fills in a default value and you lose data. Or it runs fine in development but fails in production because production has 10 million rows and development has 100. Before you run a migration on production: run it on a copy of the production database. See what happens. If it fails, fix it. If it succeeds, check that the data is correct. If it takes 10 minutes, you need a different strategy (background jobs, blue-green deployment).
Cascading deletes are a hidden trap. You delete a user. The database is configured to cascade-delete all their related records: orders, payments, messages, files. That seems reasonable. But if you misunderstood the schema, you might have just deleted orders for a different user, or every message in the system. Before you run a delete operation: write a SELECT query first to see what will be deleted. Use a transaction so if something goes wrong you can rollback. Test this in development with real data first.
Sensitive data should not leak in API responses. Your user model has a password field. A developer puts /users/:id and forgets to exclude the password from the response. Now every client request reveals a password hash. Or the API returns a user's API key when it should not. Do a manual audit: call your own API and look at what is returned. Are there fields that should not be there? Use an API tool (Postman, Insomnia, Bruno) to test endpoints and inspect the full response.
Error Handling & Observability
If you do not know an error happened, you cannot fix it. Error monitoring catches crashes before users report them. Structured logging makes debugging fast. Health checks let you know if your app is actually alive.
Error monitoring is not optional. Set up Sentry, Highlight, or Datadog. It takes 15 minutes. Every uncaught error gets sent to the service. You get an email or Slack alert. You can replay the exact state of the app when the error happened. Without this, you rely on customers to report bugs, and by then they have already left bad reviews or churned. Before you launch: deploy error monitoring. Set up notifications. Kill your app (like process.exit()) to confirm the error shows up in your dashboard.
Structured logging means logging as JSON, not as strings. Instead of console.log("User logged in"), log {"event": "user_login", "user_id": 123, "timestamp": "2026-08-21T10:00:00Z"}. This is parseable. You can search and filter logs. When an error happens, you can trace the exact sequence of events that led to it. Use a logger library (Winston, Pino for Node; structlog for Python). Set it up once, use it everywhere. Before you launch, verify that your logs are searchable using a tool like LogRocket or Supabase's logging.
Health checks are a simple endpoint that says "I am alive and can connect to the database." Monitoring tools ping this endpoint every 60 seconds. If it does not respond, you get an alert. If you use BetterUptime or UptimeRobot, this takes 5 minutes to set up. Before you launch: build a /health endpoint that returns {"status": "ok"} if your app is healthy, or an error if it is not. Test that an alert fires when you kill the database.
Graceful degradation means your app degrades instead of crashing. If an external API times out (like a payment processor), your app does not crash. It returns an error to the user: "Payment service is slow, please try again." It might queue the request for retry. It does not show an HTTP 500. Before you launch: identify all the external services you depend on (APIs, databases, file storage). For each one, ask: what if this times out? What if it is down for an hour? Plan the fallback.
Testing & Code Quality
Unit tests are good. Integration tests are better because they catch problems at the boundary between your code and the database. Authorization tests are best because they catch the security mistakes AI tools make most often.
Real assertions, not tautologies. An AI tool generates a test: expect(user).toBeDefined(). This test passes even if user is null. Write assertions that mean something: expect(user.id).toBe(123) or expect(users).toHaveLength(3). Before you launch: look at your test file. Do the assertions actually verify behavior, or do they just check that the code ran without crashing?
Integration tests hit the database. A unit test mocks the database. An integration test actually reads from and writes to a database (a test database, not production). This catches problems like "the query is syntactically valid but semantically wrong" or "the database schema has a typo." Before you launch: write integration tests for critical paths. If you are building a payment app, write a test that creates an order, adds items, calculates tax, and creates a payment record. Verify the database has the right data.
Authorization tests are the most important security test. Write tests that try to violate permissions. Create two users. User A tries to access User B's data. The endpoint should return a 403 Forbidden. Do not skip this. AI tools often forget to check who owns a resource.
Edge cases are where production breaks. A user signs up with an empty name. An order is created with quantity 0. A date field is tomorrow's date, not today's. A CSV file is empty. An image is 0 bytes. Before you launch: brainstorm 20 invalid inputs. Write tests for each one. The app should not crash; it should return a sensible error.
Performance & Scalability
A fast app keeps users. A slow app loses them. Image optimization, database indexing, and pagination are the three lowest-hanging fruit. Optimize these before you launch.
Image optimization cuts load time by 60%. Users upload a 10 MB photo. You store it as-is. The app is slow. Compress images when they are uploaded using ImageMagick, Sharp library, or CloudFlare's Image Optimization. Store multiple sizes (thumbnail, medium, full). Serve from a CDN. Before you launch: upload a large image. Measure page load time. Then optimize and measure again. You should see a dramatic difference.
Database indexing makes queries fast. You query orders by user_id. If there is no index on user_id, the database scans every row (slow). If there is an index, the database jumps to the matching rows (fast). Before you launch: identify your most common queries. Add indexes for the WHERE and JOIN columns. Test query performance with a profiler. If a query takes longer than 100ms, there is probably a missing index.
Pagination is essential. A list endpoint that returns all 10,000 records is slow and uses tons of memory. Pagination returns 50 records at a time. Before you launch: make sure every list endpoint has pagination. Use limits (50 records per page). If a user does not specify a limit, enforce a max (50). Disable full-text search on millions of rows; it is too slow.
Connection pooling reuses database connections. Every request opens a connection. If you have 100 concurrent users, that is 100 connections. Without pooling, this exhausts the database. Connection pooling reuses connections. Before you launch: configure connection pooling. If you use Supabase, it is built in. If you self-host, use pgBouncer for Postgres.
Deployment & Rollback
Deploying to production should be boring and reversible. Use a CI/CD pipeline so code changes go through automated tests before they reach production. Make rollbacks fast so if something breaks, you can revert in under 5 minutes.
CI/CD pipeline automates testing and deployment. You push code. GitHub Actions (or GitLab CI, CircleCI) runs your tests automatically. If tests pass, the code deploys to staging. A human verifies staging. If it looks good, a click deploys to production. If tests fail, deployment stops. This prevents shipping broken code. Before you launch: set up a CI/CD pipeline. At minimum: run your tests automatically. Require tests to pass before merging to main.
Environment separation means dev, staging, and production are different databases and servers. Do not test on production. Before you launch: make sure you have a staging environment that mirrors production. Run full end-to-end tests on staging before pushing to production.
Rollback capability means if you deploy a bug, you can go back to the previous version in under 5 minutes. If you deploy code and error monitoring lights up with crashes, you want to revert immediately. Most hosting platforms (Vercel, Netlify, Heroku, and Ship) support one-click rollback. Before you launch: test your rollback process. Deploy a version, break something intentionally, then rollback. Verify rollback takes less than 5 minutes.
No hardcoded environment values. Your database URL should not be in your code. Your API keys should not be in your code. Use environment variables. Before you launch: scan your code for hardcoded strings like "localhost:5432" or API URLs. Replace them with env vars.
Legal & Compliance
Privacy policies are not optional. GDPR, CCPA, and other regulations require you to tell users what you do with their data and let them delete it. This is not about litigation risk--it is about user trust. Review the GDPR checklist before launching an app for detailed compliance guidance. Also check the checklist before your first paying customer for business readiness.
Privacy policy tells users what you collect and how you use it. Before you launch: write a privacy policy. It does not need to be 40 pages. Simple is fine: "We collect your email and name so we can send you receipts. We do not sell your data. You can request deletion." Use a template (Termly, Privacy Policy Generator). Add it to your site.
Data deletion capability is required by GDPR and CCPA. A user deletes their account. You should delete their data (or mark it as deleted). Before you launch: write a script that deletes all data for a user. Test it. Make sure related records are cleaned up.
Cookie consent is required if you track users. If you use Google Analytics or PostHog, you need a consent banner. Before you launch: add a cookie consent banner (CookieBot, OneTrust). Ask users before you set tracking cookies.
Terms of Service protect you. Before you launch: write basic ToS. It does not need to be long. At minimum: "Do not use this for illegal activity. We can shut you down if you do. We are not liable for losses." Use a template.
Self-assessment: Rate yourself on each category. Do you have rate limiting? Yes (1 point). No (0 points). Count the points. 25-30 points: ship. You are good. 18-24: fix the big gaps first (security, backups, error monitoring). 10-17: delay launch. You have significant gaps. Less than 10: do not ship yet.
Frequently Asked Questions
What is production readiness?
Production readiness means your app is secure, reliable, and will not lose or leak data when real users use it. It is not about features--it is about resilience. A production-ready app crashes gracefully, not silently. It has monitoring so you know when something breaks. It has backups so data loss does not end your company. It has security so user data is not stolen.
What is the difference between a production readiness checklist and a launch checklist?
A launch checklist covers business and marketing (pricing set, payment processor configured, customer support email works). A production readiness checklist covers technical resilience (backups tested, error monitoring active, rate limiting enforced). You need both.
How long does production readiness take?
Security, error monitoring, and backups: 3-4 days if you have a working app. Testing and performance optimization: 1-2 weeks. Compliance and legal: 1-2 days. Total: 2-3 weeks for a team of 2-3 people. If you wait until after you launch, it will take longer.
Can I use managed hosting to skip some of these checks?
Managed hosting handles deployment, backups, monitoring, and scaling. You still need to check your own code for security vulnerabilities, hardcoded secrets, and authorization bugs. Ship removes the infrastructure burden so you can focus on code quality and testing.
What is the most common mistake?
Skipping error monitoring. Teams launch without Sentry or similar. A user hits a bug, the app crashes, the founder never finds out. Three days later, a customer asks why their data is missing. The team spends two days debugging while the customer is angry. This is all preventable.
What if I do not have time to do all of this?
Prioritize ruthlessly: (1) Security (no leaked credentials, auth on every route, rate limiting). (2) Error monitoring (Sentry takes 15 minutes). (3) Backups (enable, then test). (4) Basic tests on critical paths. Ship with these four. Everything else can be next week.
Do I need a security expert to review my code?
A professional security audit is expensive and can be useful. For a seed-stage startup, a better investment is automated scanning (Snyk, npm audit) and a code review by a co-founder or senior engineer who thinks adversarially. Have them ask: "How would someone misuse this? What if this API times out? What if the user sends malicious input?" That catches 80% of problems.
The Bottom Line
A working app in your IDE is not a production app. Production is traffic spikes, database corruption, attacks, third-party services timing out, and users doing unexpected things. This checklist covers the categories that separate "shipped" from "shipped and wrecked."
Most of this is not hard. Sentry, PostHog, and Docker are tools thousands of companies use. The hard part is discipline--actually doing the checks instead of assuming they will not matter. The first time your app crashes and you do not know why, you will wish you had error monitoring. The first time your database gets corrupted and you have no backup, you will wish you had tested restores.
Start with security and backups. Add error monitoring and basic tests. Everything else can be iterated. Then ship. Ready to move forward? Explore Ship's managed hosting to handle deployment, monitoring, and backups automatically so you can focus on code quality.