Credentials do not belong in your code
At some point while building your app, you needed to connect to something external. A payment processor. An email service. A database. An AI API. Each of those connections required a key or a password, and at that moment you faced a small decision: where do you put it?
The fastest option is to paste it directly into the code. It works immediately. You move on. And unless something prompts you to change it, it stays there.
This is how credentials end up in source code, and it is still one of the most reliable ways to have a bad day.
How they leak
The obvious path is a public repository. You push to GitHub, the repository is public, and anyone who finds it has your keys. Automated scanners look for exactly this, constantly, and they are fast. A credential committed to a public repo can be found and used within minutes.
Private repositories are safer but not safe. Repositories get made public by accident. They get forked. Access gets shared more broadly than intended. And the credential is still there in the git history even after you delete it from the code, because git keeps every version of every file ever committed.
There are less obvious paths too. Credentials in code end up in log output, in error messages, in stack traces that get pasted into support tickets. They end up in screenshots. They end up in the editor sessions that syncing tools back up to cloud storage.
The standard fix
Environment variables are the conventional solution and they work well. Instead of putting a value in your code, you put a name: process.env.STRIPE_SECRET_KEY rather than the key itself. The actual value lives outside the codebase and gets injected when the app starts.
On your own machine, a .env file holds the values. You add that file to .gitignore so it never gets committed. In production, the values come from wherever your hosting platform stores them.
This separates the code (which is fine to share) from the credentials (which are not). Your repository can be fully public and nothing sensitive is exposed.
Where it goes wrong in production
The local part is straightforward. The production part is where people run into trouble.
Most platforms give you a panel somewhere in their dashboard where you can enter environment variables. You navigate there, type in your key names and values, save them, and redeploy. It works, but it has a few problems.
First, the configuration is completely separate from your code. If you add a new external service and need a new credential, you have to remember to add the variable to the platform before deploying. It is easy to forget, and the failure mode is an app that starts up, calls an external service, and crashes because the variable is missing.
Second, there is no record of what variables your app depends on. New developers joining the project, or you returning to a project after six months, have to figure it out from the code or from tribal knowledge.
The PRODUCTION.md approach
In your PRODUCTION.md file, you list the names of every environment variable your app needs. Just the names, not the values. This is safe to commit because the names themselves are not sensitive.
The values you supply to Prodwise separately, once, through an encrypted channel. After that, they are stored securely and injected into your app at runtime. They never appear in your repository, in your build logs, or in your deploy output.
The list of names in your PRODUCTION.md serves as documentation. Anyone looking at the project can see exactly what it depends on. If a variable is missing, we tell you before the deploy rather than letting the app start up and fail.
If you have already committed credentials
The first thing to do is rotate them. Go to whichever service issued the credential and generate a new one. The old value is compromised regardless of whether it has been used yet. Assume it has been found and act accordingly.
Removing it from the code and committing that change is not enough: it is still in the git history. You can rewrite the history to remove it, but that is disruptive if others are working from the repository. The more practical answer is to treat rotation as the fix and rely on the new credential being stored properly from now on.
Prodwise handles secret storage for every app we host, on all plans. You list the names in PRODUCTION.md and supply the values once. We take it from there.