Environment Variables & Secrets
🔐 Environment Variables & Secrets
Never Commit Credentials
The same "never expose secrets" principle running through the site's security courses applies directly here. A credential committed to git history is compromised forever, even if later deleted from the current file — git retains every prior commit. This is a genuinely common real-world incident pattern: automated bots scan public GitHub pushes and scrape leaked API keys within minutes of being committed.
🔑 GitHub Secrets
Where credentials actually belong instead — stored encrypted at the repo or organization level, added via Settings → Secrets and variables → Actions, referenced in workflows via secrets.SECRET_NAME. GitHub automatically redacts a secret's value if it appears in log output, replacing it with ***.
Using a Secret in a Workflow
This is how a deploy step gets access to a real production credential without that credential ever touching the repo's own files.
Environment-Specific Configs
Dev, staging, and production typically need different values for the same conceptual setting — a different database URL per environment. GitHub Environments (Chapter 8) let secrets be scoped per environment, so "the production DATABASE_URL" and "the staging DATABASE_URL" can share a name but hold different values depending on which environment a job is deploying to.
🛡️ Secret Scanning as Defense in Depth
GitHub's built-in secret scanning automatically detects patterns matching known credential formats (AWS keys, common API token shapes) if accidentally pushed, and can alert or block the push. This is a safety net, not a replacement for keeping secrets out of the repo in the first place — defense in depth means multiple independent layers, not relying on just one.
Plain Env Var vs Real Secret
A non-sensitive value like NODE_ENV can be hardcoded directly in the workflow. A real credential must always go through secrets.*.
Local Development Equivalent
A local .env file plus a .gitignore entry for it is the local-machine version of the same principle — credentials live outside version control everywhere, not just in CI.
Hardcoded vs Secret Reference
Hardcoded in YAML
Visible to anyone with repo read access, and permanently committed to git history — recoverable forever, even after deletion.
secrets.DATABASE_URL
Encrypted, redacted in logs, and never present in the repo's files or git history at all.
💻 Coding Challenges
Challenge 1: Fix a Hardcoded API Key
A workflow step reads run: curl -H "Authorization: Bearer sk_live_abc123" https://api.example.com. Rewrite it using a GitHub Secret.
Goal: Practice the single most fundamental secrets fix.
Challenge 2: Explain Why Deleting a Committed Secret Isn't Enough
A developer commits an API key by mistake, notices immediately, and deletes it in the very next commit. Explain why this key should still be treated as compromised.
Goal: Practice reasoning about git history's permanence, not just the current file state.
Challenge 3: Choose Which Values Need secrets.*
For each of (a) NODE_ENV: production, (b) a Stripe secret key, and (c) a public API base URL like https://api.example.com, decide whether it needs to go through secrets.* or can be hardcoded in the workflow.
Goal: Practice distinguishing genuinely sensitive values from ordinary configuration.
GitHub's automatic redaction only works for the exact known secrets.* value it's tracking — if a script explicitly echoes it (run: echo "Connecting with $DATABASE_URL" for debugging), or constructs a similar-looking string from parts of the secret in a way redaction doesn't recognize, the value can leak into logs anyway. Treat automatic redaction as a backstop, not a guarantee — the safest practice is to never intentionally print a secret's value at all, even temporarily for debugging.
🎯 What's Next
With credentials handled safely, the next chapter brings containers into the pipeline: Docker in CI/CD — building and pushing an image as a pipeline step, reusing the multi-stage build pattern from earlier deployment chapters.