Environment Variables & Secrets

CI/CD Pipelines — Environment Variables & Secrets
CI/CD Pipelines
Chapter 5 · Environment Variables & Secrets

🔐 Environment Variables & Secrets

Chapter 4 covered building and testing. This chapter covers something every real pipeline eventually needs — credentials — and how to handle them without ever putting them in the repository itself, extending the environment/configuration material from the Node.js and TypeScript courses.

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

jobs: deploy: runs-on: ubuntu-latest steps: - run: ./deploy.sh env: DATABASE_URL: ${{ secrets.DATABASE_URL }}

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.

→ Solution

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.

→ Solution

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.

→ Solution

⚠️ Gotcha: Accidentally Logging a Secret Anyway

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.