Lesson 11 of 12 · Ship it

Check it before you ship it

The pre-flight checklist a CTO runs before an app goes live: secrets out of Git, a security read-through, real tests, a production build, a README.

30 min You end with: A project that builds for production, has tests for the risky paths, has no secrets in its history, and a README a stranger could follow.

Everything so far has been on your laptop, visible only to you. The next lesson puts it on the internet, where anyone with the URL can reach it and scanners look for mistakes within minutes of a deploy. This lesson is the thirty minutes between those two states. It is the part most people skip. Do not skip it.

Everything running (Lesson 6, “Stopping and coming back”).

Step 1: Secrets. Not just now, ever.

You checked .env before every commit. Now check the whole history, because a secret committed once and deleted later is still in the history.

Search the entire Git history of this project, every commit, for anything that looks like a secret: .env files, database URLs with passwords, API keys, tokens. Report what you find and in which commits. Do not change anything yet.

If it finds nothing, good. If it finds something, the fix has two parts, and the second one is the one people forget:

  1. Remove it from the history. Claude Code can do this. It rewrites the project’s history and overwrites the copy on GitHub, so commit everything first, and do not do it if anyone else has a copy of the repository. If that sentence worries you, this is a good one to ask a person about. The exposed API keys guide walks through it.
  2. Change the secret. A password that was ever in Git is burned, even after you scrub it. For your local localdev password that does not matter. For anything real, it does.

Then, for the future:

Make sure .gitignore covers .env, .env.local, .env.*.local and any other file that could hold secrets. Confirm .env.example contains placeholders only.

Step 2: A security read-through

This is not a scan. You are asking for a read. (How to audit a vibe-coded app’s security explains why the difference matters.)

Read this whole project as a security reviewer would, knowing it is about to be deployed to a public URL with no login. List every way someone could see, change or delete data they should not, or make the app misbehave. For each one: how bad is it (low, medium, high), and what is the smallest fix. Do not change anything yet.

You will get a list. Expect at least these:

  • No authentication. Anyone with the URL can see and edit every client. This is the big one, and it is correct: the app has no login. Lesson 12 keeps the deploy private until you add one; adding a login is a natural next project after this course.
  • No rate limiting. Someone could add ten thousand clients in a minute. For a private tool, low. Note it.
  • Input length. Notes could be a megabyte. Ask for a sane limit.

Fix the small ones now, one per prompt:

Limit name, company and email to 200 characters and notes to 5000, validated on the server. Return a readable error if exceeded.

Leave authentication for after the course; it is a project of its own. But do not forget the answer: without a login, this app is not for real client data on a public URL.

Step 3: Tests for the paths that matter

Tests are small programs that use your app the way you did in Lesson 10 and complain if something breaks. You do not need many. You need them for the things that would hurt. (How to test a vibe-coded app goes further when you are ready.)

Add automated tests for the server-side logic only:
1. Creating a client with a valid name succeeds and it can be read back.
2. Creating a client with an empty name is rejected.
3. Creating a client with a status outside the four allowed values is rejected.
4. Deleting a client removes it.

Use a lightweight test setup that fits this project, running against the local Docker database (a separate test database is fine if you think it is cleaner; explain). Add an npm script called "test" that runs them. Tell me the plan first.

Read the plan and approve it with manual edits. One thing to know: the tests add and delete rows in your local database while they run. That is fine; it only holds fake clients. If it bothers you, ask for the separate test database. Then run them:

Run the tests and show me the result.

Four passing tests. Now break one on purpose, the way you broke Git in Lesson 5:

Temporarily change the server validation so an empty name is allowed, run the tests, show me which one fails, then put the validation back and run them again.

You just watched a test catch a bug. That is the whole point. From now on, “run the tests” is something you say after any change to the server code.

Step 4: The production build

The dev server is forgiving. The production build is not, and Vercel will run it in Lesson 12. Run it yourself first. Go to Terminal 1 and press Ctrl + C to stop the dev server (the production version needs the same port). In that same window:

npm run build

It takes a minute and should end without errors. Warnings are usually fine. Errors are not; paste the whole output into Claude Code: “The production build failed with this. Fix it, and run the build again to confirm.”

Then prove the built app runs:

npm run start

Open localhost:3000. Same app, production mode. Press Ctrl + C to stop it, then npm run dev to get the dev server back.

Step 5: A README

A README is the note on the fridge for whoever opens this project next: you in six months, a developer you hire, or a CTO you ask for help.

Write a README.md for this project for a non-developer. Include: what the app is, the exact steps to run it from a fresh clone (install, start the database, set up .env from .env.example, run migrations, start the dev server), how to run the tests, how to reset the database, and how to open Prisma Studio. Keep it short and honest. Mention that there is no authentication yet.

Read it. If you could not follow it on a new laptop, say what is unclear. This document is worth more than it looks: it is the difference between a project someone can pick up and a project nobody can.

Step 6: Commit

Show me what would be committed, confirm no .env, and commit with the message "Pre-ship checks: validation limits, tests, README". Push.

Check your work

  • The history search found no secrets (or you removed them and rotated them).
  • You have a written list of security findings and know which ones are still open. The open one is “no login.”
  • Four tests exist and pass. You saw one fail and pass again.
  • npm run build succeeds.
  • README.md exists and you could follow it.
  • Committed and pushed.

If something went wrong

The build fails with type errors. Common after a lot of AI edits. Paste the errors in. If there are many, ask it to “fix them one file at a time, running the build after each, and stop if a fix changes behavior.”

Tests cannot connect to the database. Docker is not running, or the tests use a different database name. “The tests fail to connect. Show me the DATABASE_URL the tests use and make them work against the local Docker database.”

Tests pass even when I break the validation. The test is checking the wrong thing. “This test still passes with validation removed, so it is not testing validation. Fix the test so it fails when empty names are allowed.” Then break it again to prove it.

The security list is long and scary. Most of it will be medium and low. Fix the small ones. Write down the big ones. Long lists are normal; what is not normal is never asking for the list.

Next

Lesson 12 puts it on the internet: GitHub to Vercel, a hosted Postgres, the migration running in the cloud, and a URL you can open on your phone.

Did this lesson not go to plan?

A CTO can help you starting today.

Screenshot the error, note which step you were on, and request a call. Every member starts with a call: bring what you built and where it stopped, and that is where we work out whether we can help and what to do first. No card. No sales deck.

Request a call with your CTO

Bring it to a CTO.

The error, the screenshot, or the thing you are afraid to touch. Every member starts with a call, and that is where we work out whether we can help and what to do first.

Request a call with your CTO

Every member starts with a call. No card.