Profile    Mohammed Shiroz Status   Loading  
Logo
Share This
Back to blog
Filter by:
Tags
//Article title

Environment Variables and Secrets: Keeping API Keys Out of Your Code (and Git History)

About Post

A developer notices an API key committed to the repository. They delete it, commit "remove key", push, and relax.

The key is still there. It's in the previous commit, in every clone, in every fork, and possibly in the logs of whatever bot scanned the repository while it was public. Deleting a secret from your code doesn't un-leak it.

Secrets are one of those topics everyone thinks they understand until the day something goes wrong. So let's go through how they should live in an app, where they actually leak, and what to do in the first hour after a leak.

The basic rule: config comes from the environment

Code is the same everywhere. Configuration is what changes between local, staging and production: database passwords, API keys, mail servers. The Twelve-Factor App guidelines put it neatly: store config in the environment, not in the code.

In practice, for most PHP and Node projects, that means a .env file locally and real environment variables (or a generated .env) on servers. Laravel gives you this from day one:

  • .env holds real values and is listed in .gitignore. Never commit it.
  • .env.example is committed, with every key and placeholder values, so a new developer knows what to fill in.

The habit that keeps this honest: any pull request that adds a new variable also adds it to .env.example. Reviewers should reject a new config key without one.

The Laravel trap: env() after config caching

In production you run php artisan config:cache, which compiles all config files into one cached PHP file. After that, Laravel stops loading .env, and any env() call outside the config/ folder returns null.

// Wrong: works locally, returns null in production after config:cache
$key = env('PAYMENT_API_KEY');

// Right: config/services.php
'payment' => [
    'key' => env('PAYMENT_API_KEY'),
],

// Right: anywhere else in the app
$key = config('services.payment.key');

Read env() only in config files. Use config() everywhere else. This one rule removes a whole category of "it works locally" bugs, and it also gives you one place to see every secret the app depends on.

Where secrets actually leak

Committing .env is the famous mistake. These are the quieter ones I see more often:

  • Frontend build variables. In a Vite project, anything prefixed VITE_ is compiled into the JavaScript bundle and readable by anyone. The same goes for keys baked into a mobile app. If it ships to the client, it's public. Only publishable keys belong there.
  • Debug pages. APP_DEBUG=true in production can expose configuration and stack details to anyone who triggers an error.
  • Logs. Logging a whole request or an outgoing HTTP call can write tokens and passwords into files that many people can read.
  • CI output and Docker images. An echo in a pipeline step, or a .env copied into an image layer, survives longer than you think.
  • Copy and paste. Screenshots in tickets, snippets in chat, a config file pasted into an AI assistant to "help debug".

Beyond .env: secret managers

A .env file on a server is fine for many apps. It starts to hurt when you have several servers, several people with access, and keys that need rotating. That's when a secret manager pays for itself: AWS Secrets Manager or Systems Manager Parameter Store, HashiCorp Vault, or your cloud's equivalent.

What you gain:

  • Access control and audit. You can see who or what read a secret, and limit it per service.
  • Rotation without redeploying by hand. Update the value in one place.
  • No secrets on laptops. Developers don't need production values at all.

Usually the deploy pipeline fetches the values and writes them into the environment before config:cache runs, so the app itself stays simple.

And the best secret is the one you don't have. On AWS, an EC2 instance can use an IAM role to reach S3 or other services, so there is no access key to store, leak or rotate. Prefer that over long-lived keys wherever the platform allows it.

The rule that saves you: treat every secret as something that will leak one day. Give it the smallest permissions possible, make it easy to rotate, and know exactly where it's used.

A key leaked. Now what?

Order matters here. Most people instinctively start by cleaning the code. That's step four.

  1. Revoke or rotate the key immediately. Generate a new one, update production, then disable the old one. Until the old key is dead, nothing else matters.
  2. Check what it was used for. Look at the provider's usage logs or your cloud's audit trail for activity you don't recognise.
  3. Find out how it leaked. A commit, a log, a bundle, a screenshot? The cause decides the real fix.
  4. Clean up. Remove it from the code. If the repo was public, rewriting history is good hygiene, but assume the key is compromised regardless.
  5. Add a guard. Enable secret scanning (GitHub offers secret scanning and push protection), or add a pre-commit scanner like gitleaks.

One Laravel-specific note: rotating APP_KEY breaks existing encrypted data and sessions unless you plan for it. Since Laravel 11 you can list old keys in APP_PREVIOUS_KEYS so data encrypted with them can still be decrypted while you move over. The encryption docs explain how it works.

Checklist

  • .env ignored, .env.example complete and reviewed.
  • env() only inside config/.
  • No secrets in frontend variables, mobile bundles, logs or CI output.
  • APP_DEBUG=false in production.
  • IAM roles instead of static cloud keys where possible.
  • A written, boring plan for rotating every key you own.

Which leak path surprised you the first time you saw it? For me it was frontend build variables: the name looks like config, the result is public JavaScript.

Comments (0)
Leave your review

Thanks for your valuable comments. Your comments has been updated and appreciate your getting in touch...

01. About Shiroz

Mohammed Shiroz

Hi, I'm Mohammed Shiroz, a software engineer and AI enthusiast from Sri Lanka who turns ideas into intelligent, real-world solutions. With over 9 years of hands-on experience, I currently lead real estate ERP development at Kate Group, a...

03.My Projects

04. Categories

Ready To order Your Project ?

Get in Touch
Close