Keep secrets out of code

Read secrets from env, fail fast if one is missing, never commit .env.

From security-best-practices: Step 4, Manage secrets.

Find the hardcoded secrets

Edit the code; the right side reads from env.

HardcodedBad
Read from envGood

        
    // src/lib/config.ts
    function required(name: string): string {
      const value = process.env[name];
      if (!value) throw new Error(`${name} environment variable is required`);
      return value;
    }
    
    export const config = {
      databaseUrl: required("DATABASE_URL"),
      stripeSecretKey: required("STRIPE_SECRET_KEY"),
      sendgridApiKey: required("SENDGRID_API_KEY"),
    };

    Keep .env out of the commit

    Edit .gitignore until only the template ships, then commit.

    .gitignoreEdit
    .env*!.env.example
    git add -ABlocked

      # .gitignore
      .env
      .env.*
      !.env.example
      
      # Already committed? Untrack it, then rotate the key.
      # git rm --cached .env

      Boot with a missing variable

      Set the variables, boot both, then send a request.

      Read when usedBad
        Checked at startupGood
          // before app.listen()
          const dbUrl = process.env.DATABASE_URL;
          if (!dbUrl) {
            throw new Error("DATABASE_URL environment variable is required");
          }
          const jwtSecret = process.env.JWT_SECRET ?? "";
          if (jwtSecret.length < 32) {
            throw new Error(`JWT_SECRET must be at least 32 characters (got ${jwtSecret.length})`);
          }
          // generate one: openssl rand -base64 32
          
          # Kubernetes Secret
          apiVersion: v1
          kind: Secret
          metadata:
            name: myapp-secrets
          type: Opaque
          stringData:
            database-url: postgresql://user:password@postgres:5432/mydb
            jwt-secret: your-jwt-secret

          Why it matters, pros and cons

          Why it matters

          A key in source lives in every clone, and in history forever.

          Pros

          • One build, different values per environment.
          • Rotating a key is a restart, not a commit.
          • A bad deploy fails; the old release keeps serving.

          Cons

          • Env vars still leak via crash dumps and child processes.
          • NEXT_PUBLIC_ and VITE_ ship values to the browser.
          • Regex scanners miss custom formats; add push protection.