Skip to main content
GitHub Actions
CHAPTER 10

Environment Variables and Secrets

Updated: May 15, 2026
25 min read

# CHAPTER 10

Environment Variables and Secrets

1. Introduction

A CI/CD pipeline is an incredibly powerful robot, but it is entirely helpless without credentials. To pull proprietary code, it needs a Git Token. To upload a Docker image, it needs a Docker Hub password. To deploy an application, it needs an AWS Access Key. If these credentials are mishandled—such as being typed directly into a pipeline script and committed to version control—your entire cloud infrastructure can be compromised in minutes. In this chapter, we will master the secure management of credentials, exploring how to inject Configuration via Environment Variables, and how to safely store and retrieve highly sensitive data using CI/CD Secrets Management.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Distinguish between standard Environment Variables and Encrypted Secrets.
  • Set and utilize Environment Variables dynamically within a pipeline.
  • Securely store credentials in GitHub Repository Secrets (or Jenkins Credentials).
  • Inject Encrypted Secrets into pipeline steps safely.
  • Understand the risks of Secret leakage in CI/CD log files.

3. Beginner-Friendly Explanation

Imagine giving instructions to a house sitter.
  • Environment Variable (Public Info): You leave a sticky note on the fridge: "The thermostat is set to 72 degrees. The dog's name is Buster." Anyone walking through the house can read this. It configures how the house operates, but it's not a secret.
  • Secret (Encrypted Data): You have a locked safe in the basement containing the keys to your Ferrari. You give the house sitter the combination, but tell them never to write it down.

In a CI pipeline, the Node.js version is an Environment Variable. The AWS Database Password is a Secret. Both are injected into the pipeline at runtime, but Secrets are heavily guarded.

4. Standard Environment Variables

Environment variables (env) are used to configure the behavior of your tools without hardcoding values into the command scripts. They can be defined at the Workflow level, the Job level, or the Step level.
yaml
1234567891011121314
name: Environment Variable Demo

# Define globally. Available to all jobs.
env:
  NODE_ENV: production
  APP_PORT: 8080

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Print Variables
        # The variables are injected into the bash shell
        run: echo "Deploying app in $NODE_ENV mode on port $APP_PORT"

5. Managing Encrypted Secrets

Secrets must never be written in the YAML file. In GitHub Actions, you navigate to Settings > Secrets and variables > Actions in your repository and create a "New repository secret" (e.g., Name: AWS_ACCESS_KEY, Value: AKIAIOSFODNN7EXAMPLE).

Once saved, the value is permanently encrypted. You cannot even view it again in the UI; you can only update it or delete it.

To use the secret in your pipeline, you use the ${{ secrets.SECRET_NAME }} context.

yaml
123456
      - name: Deploy to Cloud Provider
        run: ./deploy_script.sh
        # Inject the secrets securely into the step's environment
        env:
          AWS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
          AWS_SECRET: ${{ secrets.AWS_SECRET_KEY }}

6. Mini Project: Configure Encrypted Deployment Secrets

Let's build a pipeline step that requires an API key to notify a third-party service (like Slack) that a deployment has finished.

Step-by-Step Architecture Concept:

  1. 1. Generate an API token from your external service (e.g., xoxb-12345-secret-token).
  1. 2. Save it in GitHub Secrets as SLACK_BOT_TOKEN.
  1. 3. Create the workflow:

yaml
1234567891011121314151617181920
name: Secure Notification Pipeline

on: [push]

jobs:
  notify-team:
    runs-on: ubuntu-latest
    steps:
      - name: Send Slack Notification
        uses: slackapi/slack-github-action@v1.24.0
        with:
          # The Action reads this specific payload
          payload: |
            {
              "text": "Deployment to Production is complete! :rocket:"
            }
        env:
          # The Action REQUIRES the SLACK_WEBHOOK_URL to be in the environment.
          # We securely inject it from our encrypted vault.
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

*The API token is never exposed in the code, yet the pipeline successfully authenticates to Slack and sends the message.*

7. Real-World Scenarios

A developer was trying to debug a script that uploaded files to an AWS S3 bucket. The script wasn't authenticating correctly. To debug it, the developer added a command to the CI pipeline: run: echo "Using password: ${{ secrets.AWS_SECRET_KEY }}". They expected to see the password in the Jenkins console logs to verify it was correct. However, modern CI tools are smart. When the pipeline ran, the log output literally printed: Using password: *. CI tools actively scan standard output and automatically mask (redact) any string that matches a known Secret. This built-in security feature prevented the developer from accidentally leaking the master AWS password to the entire company's log viewer.

8. Best Practices

  • Least Privilege Principle: Never create a single, master AWS IAM user with "Administrator Access" and put its keys in your CI/CD Secrets. If the CI pipeline is hacked, the attacker owns your entire company. Create a highly restricted IAM user that ONLY has permission to upload files to a specific S3 bucket, and use *those* keys in the CI pipeline. If leaked, the damage is heavily contained.

9. Security Recommendations

  • Avoid Command-Line Arguments for Secrets: Never pass a secret directly as a command-line argument.
  • *DANGEROUS:* run: my_script.sh --password ${{ secrets.DB_PASS }}
If the script crashes, the OS might log the command executed, exposing the password in plain text.
  • *SECURE:* Pass the secret as an Environment Variable (as shown in the Mini Project section). Operating systems do not typically log environment variables in crash dumps.

10. Troubleshooting Tips

  • Empty Secrets: If a pipeline script fails with an "Authentication Denied" or "Missing Password" error, but the YAML looks perfect, the most common culprit is a typo in the Secret name. If you write ${{ secrets.AWS_KEY }} but the secret is actually named AWS_ACCESS_KEY, GitHub will simply inject a blank empty string ("") without throwing a YAML error, causing silent authentication failures.

11. Exercises

  1. 1. What is the fundamental security difference between defining a value in the env: block of a YAML file versus storing it in GitHub Secrets?
  1. 2. Why is passing a secret via an Environment Variable considered safer than passing it as a direct terminal argument (e.g., -p MySecret)?

12. FAQs

Q: Can I share secrets between different repositories? A: In GitHub, you can create "Organization Secrets." These are defined at the company level and can be made available to dozens of different repositories simultaneously, preventing you from having to manually copy the same AWS key into 50 different projects.

13. Interview Questions

  • Q: Describe the mechanism by which highly sensitive credentials (like AWS Secret Keys) should be injected into a CI/CD pipeline. Why is committing these credentials to version control a catastrophic security failure?
  • Q: Explain how modern CI platforms (like Jenkins or GitHub Actions) handle log masking to prevent the accidental exposure of injected Secrets. What coding practices can inadvertently bypass this masking?

14. Summary

In Chapter 10, we secured the most vulnerable attack vector of automated pipelines: credential management. We differentiated between public Configuration (Environment Variables) and highly sensitive Authentication data (Encrypted Secrets). By utilizing native vault mechanisms (like GitHub Secrets), we completely decoupled our passwords and API tokens from our version-controlled YAML files. We learned the critical best practice of injecting secrets via the environment rather than command-line arguments, and we verified that modern CI platforms actively mask these credentials in execution logs, ensuring our pipelines remain both powerful and impenetrable.

15. Next Chapter Recommendation

Our pipeline builds, tests, secures, and packages the code. But right now, we have to stare at the GitHub screen to know if it finished. How do we make the robot tell us when it's done? Proceed to
Chapter 11: Notifications and Monitoring**.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·