Client Area

Environment Variables & Secrets Management for Web Apps

ByDomain India Team
6 min read22 Apr 20263 views

In this article

  • 1Why this matters
  • 2The `.env` file pattern
  • 3PHP — loading env vars
  • 4Node.js — loading env vars
  • 5Production secrets — beyond `.env`

Environment Variables & Secrets Management for Web Apps

Every web application breach that starts with "API keys found in public GitHub repo" is a story about bad secrets hygiene. This article covers how to handle API keys, database passwords, and other sensitive values safely — using environment variables, .env files, and the right git discipline — for both PHP and Node.js stacks.

Why this matters

The 12-factor app methodology puts config (anything that varies between deploys) into environment variables. In practice this means a single idea: your codebase should run the same way on your laptop, on staging, and on production — only the environment changes.

The alternative — hardcoding credentials in source — causes the specific failure mode of public-repo leaks. Bots scan GitHub constantly for strings that look like AWS keys, Stripe secrets, Razorpay keys. A leaked credential is abused within minutes.

Environment variables solve this cleanly.

The .env file pattern

A .env file is a plain-text file at your project root containing KEY=VALUE lines:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

DB_HOST=localhost
DB_NAME=myapp_production
DB_USER=myapp
DB_PASSWORD=pl3A5e-ch4nGe-m3

STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

Rule #1 — never commit this file. First line of every .gitignore:

.env
.env.local
.env.production

Rule #2 — commit a `.env.example` instead. Same keys, but safe placeholder values:

APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost

DB_HOST=localhost
DB_NAME=myapp_local
DB_USER=changeme
DB_PASSWORD=changeme

STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

When a new developer joins, they copy .env.example to .env and fill in their local values.

PHP — loading env vars

The standard library is vlucas/phpdotenv:

bash
composer require vlucas/phpdotenv

At the top of your app bootstrap:

php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

// Access values:
$dbPassword = $_ENV['DB_PASSWORD'];
$stripeKey  = $_ENV['STRIPE_SECRET_KEY'];

Use $_ENV (not getenv()) — it is faster and thread-safe.

Laravel includes phpdotenv automatically. Access via env('DB_PASSWORD') at config-load time, or via config('database.connections.mysql.password') at runtime. When config:cache has been run, calls to env() outside config files return null — always wrap env-var reads in config/*.php files.

Node.js — loading env vars

bash
npm install dotenv

First line of your entry file:

javascript
require('dotenv').config();
// or ES modules:
import 'dotenv/config';

// Access:
const dbPassword = process.env.DB_PASSWORD;
const stripeKey  = process.env.STRIPE_SECRET_KEY;

For framework-specific handling: Next.js reads .env.local, .env.development, .env.production automatically — no dotenv needed.

Add basic validation on startup so missing vars fail fast:

javascript
const required = ['DB_PASSWORD', 'STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'];
const missing = required.filter(k => !process.env[k]);
if (missing.length) {
  console.error(`Missing required env vars: ${missing.join(', ')}`);
  process.exit(1);
}

Production secrets — beyond .env

On a live server, you have three places to put your production env vars:

1. A .env file on the server (simplest)

The file lives at /home/youruser/app/.env, owned by your app user, chmod 600 (readable only by owner). It was never committed to git — you uploaded it once manually or via a deploy script that reads from a secret store.

2. Environment variables at the process level

On cPanel: Environment Variables section (if your plan exposes it).

On a VPS with systemd:

ini
# /etc/systemd/system/myapp.service
[Service]
Environment=DB_PASSWORD=xxx
Environment=STRIPE_SECRET_KEY=sk_live_...
# OR reference an env file:
EnvironmentFile=/home/myapp/.env

3. A dedicated secret store (for teams)

For larger teams: HashiCorp Vault, AWS Secrets Manager, Doppler. Overkill for a one-dev shop, sensible once you have production secrets rotating across multiple environments.

Deploy-time secrets (CI/CD)

If you use GitHub Actions or similar:

  • Never put secrets in the workflow YAML.
  • Add them under repo → Settings → Secrets and variables → Actions.
  • Reference as ${{ secrets.STRIPE_SECRET_KEY }} in the workflow.

Example:

yaml
- name: Deploy
  env:
    DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
  run: ./deploy.sh

Secrets in GitHub are encrypted at rest and masked in logs.

What belongs in env vars vs config files

KindWhereExample
Secrets (credentials, API keys, tokens)env varSTRIPE_SECRET_KEY
Feature flagsenv var or dedicated flag serviceFEATURE_NEW_CHECKOUT=true
Per-environment URLsenv varAPP_URL, DATABASE_URL
Static UI copyconfig file (committed)sidebar.menu.title
Non-sensitive constantsconfig filePAGE_SIZE=25

Rule of thumb: if changing the value requires a security review, it is a secret.

Rotating secrets

Secrets get old. Rotate them:

  • Database passwords — every 90 days for sensitive apps
  • API keys (Stripe, Razorpay, SendGrid) — on employee departure, on any suspicion of leak, annually as hygiene
  • Session secret / JWT signing key — on any breach; understand this will log out all users

Zero-downtime rotation procedure:

  1. Generate the new secret in the provider's dashboard.
  2. Deploy the new value to production (staging first if you can).
  3. Verify the app is working with the new value.
  4. Revoke the old value in the provider's dashboard.

Never revoke before deploying.

Detecting committed secrets

Tools that help:

  • git-secrets — pre-commit hook that blocks commits containing common secret patterns.
  • GitHub secret scanning — free, always on for public repos, alerts the repo owner.
  • trufflehog — scans full git history for secrets (useful when auditing a repo).

Install git-secrets on your dev machine:

bash
brew install git-secrets   # macOS
# Linux: clone from https://github.com/awslabs/git-secrets
git secrets --install
git secrets --register-aws

What to do if you committed a secret by mistake

Order of operations (critical):

  1. Revoke the secret first. In Stripe / Razorpay / AWS console, rotate the key immediately. Do this BEFORE rewriting git history — the damage is live exposure, not the git record.
  2. Assume breach. Review your provider's logs for unexpected usage since the commit was pushed.
  3. Rotate and deploy the new secret.
  4. (Optional) Rewrite git history to remove the secret from the repo — git filter-repo or bfg-repo-cleaner. Note: this does not help if the repo was ever public, because anyone who cloned already has the bad value.
  5. Tell anyone who had access to the repo that the old secret is invalidated.

Frequently asked questions

Is `.env` encrypted?

No. .env is plain text. The security model is file permissions (chmod 600) and keeping it out of git. For encrypted secret-at-rest, use a dedicated secret store.

Can I `export` env vars in `~/.bashrc` instead of using `.env`?

You can, but it makes onboarding and scripted deploys harder. Prefer .env or a managed secret store — they travel with the project.

What about Docker?

Pass env vars via --env-file to docker run, or under environment: / env_file: in docker-compose.yml. Same principle — never bake secrets into the image layers.

How do I share secrets with teammates without emailing them?

Use a password manager (1Password, Bitwarden) with shared vaults. Or a secret manager like Doppler. Never paste into Slack / email.

Is it safe to log env vars for debugging?

Only if the var is non-sensitive. Never console.log(process.env) — logs often get shipped to monitoring systems, grep-able by more people than you think.

Should I commit `.env.production`?

No. Only .env.example. Production values belong on the production server or in the CI secret store, never in git.


Got questions? [email protected]. We help hosting customers audit env-var setups on request.

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket