Back to Blog
Lesson 17 of the CI/CD: Continuous Integration from Scratch course
DevOpsJuly 17, 20264 min read

Handling Environment Variables in GitHub Actions Pipelines

Master environment variables in GitHub Actions to decouple config from your code. Learn to define workflow-level variables and map secrets securely.

DevOpsCI/CDGitHub ActionsConfigurationSecurity
Close-up of colorful programming code on a computer screen, showcasing digital technology.

Previously in this course, we covered managing secrets, which allows you to store sensitive data like API keys without exposing them in your repository. In this lesson, we build on that foundation by learning how to inject non-sensitive configuration data into your workflow using environment variables.

When you hardcode values like database URLs, feature flags, or API base paths into your workflow YAML, you create "brittle" pipelines. If those values change, you have to edit your code. Using environment variables allows you to change behavior without touching the logic, mirroring how you might manage configuration in your local application development, such as using .env files.

Understanding Environment Variables in CI/CD

Environment variables are key-value pairs available to the processes running in your CI/CD runner. They provide a standardized way to pass configuration to your scripts, whether those scripts are shell commands or application tests.

In GitHub Actions, you can define these variables at three different levels:

  1. Workflow level: Available to all jobs in the workflow.
  2. Job level: Available only to all steps within a specific job.
  3. Step level: Available only to the specific step where they are defined.

This hierarchy follows the principle of least privilege—only define the variable at the scope where it is actually needed.

Worked Example: Injecting Configuration

Let’s update our project’s pipeline to use an environment variable for an API endpoint. Instead of hardcoding the URL in our test script, we will define it in the YAML and access it through the shell.

YAML
name: Configuration Example

on: [push]

env:
  # Workflow-level environment variable
  API_ENDPOINT: "https://api.myapp.com/v1"

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Print Config
        env:
          # Step-level mapping of a secret to an env var
          API_KEY: ${{ secrets.MY_API_KEY }}
        run: |
          echo "Connecting to $API_ENDPOINT"
          # We use the env var in our shell command
          ./run-tests.sh --key $API_KEY

In this example, $API_ENDPOINT is available to every step in the workflow because it is declared at the top level. $API_KEY, however, is scoped strictly to the "Print Config" step. This is a secure practice: by mapping secrets to step-level variables, you ensure they aren't accidentally logged or accessed by other steps that don't require them.

Hands-on Exercise

  1. Open your current workflow file in .github/workflows/.
  2. Define an environment variable at the job level called APP_ENV and set it to staging.
  3. Add a step that uses the run keyword to print the value of APP_ENV using echo "Running in $APP_ENV mode".
  4. Commit your changes and verify the output in the GitHub Actions "Run" logs.

Common Pitfalls

  • Case Sensitivity: On Linux runners (like ubuntu-latest), environment variables are case-sensitive. $api_endpoint is not the same as $API_ENDPOINT.
  • Over-scoping: Avoid defining everything at the workflow level. If only one job needs a specific configuration, define it at the job level to keep your configuration clean and debuggable.
  • Logging Secrets: While GitHub Actions automatically masks secrets in the logs, if you manually print an environment variable that contains a secret (e.g., echo $SECRET_KEY), GitHub might not be able to mask it effectively. Never echo secrets to the terminal.
  • Variable Expansion: Remember that environment variables are expanded by the shell. If you are using Windows runners, the syntax differs (e.g., %VAR% vs $VAR), though GitHub Actions handles most of this abstraction for you.

Frequently Asked Questions

Q: Should I put all my configuration in environment variables? A: Use them for operational settings (like API URLs or log levels). For application-specific configuration that is highly complex, consider a dedicated config file managed via your code.

Q: Can I override a workflow-level variable in a specific job? A: Yes. If you define a variable with the same name at the job level, it will override the workflow-level value for that specific job.

Q: How do I handle environment variables when building container images? A: You need to use ARG or ENV instructions in your Dockerfile. We will explore how to pass these variables from your CI pipeline into your Docker build process in upcoming lessons.

Recap

We’ve successfully moved away from hardcoding values. By defining environment variables at the workflow, job, or step level, you’ve increased the portability of your pipeline. You also learned to securely map secrets to variables, keeping your credentials safe while keeping your workflows dynamic.

Up next: Introduction to Linting — we will start enforcing code quality automatically.

Similar Posts