Skip to main content
Environment variables in GitHub Actions let you pass configuration, secrets, and runtime values into your workflows. This guide explains where to define environment variables, how scoping and precedence work, common pitfalls (especially with CI tests), and best practices for secrets. This article covers:
  • Job-level environment variables (scoped to a single job)
  • Workflow-level environment variables (shared across jobs)
  • Step-level environment variables (scoped to a specific step)
  • Common runtime errors caused by missing env vars
  • Best practices for secrets and CI databases

Scopes and precedence

GitHub Actions supports env at three levels:
  • Workflow-level: declared at the top level, available to every job and step unless overridden.
  • Job-level: declared inside a job; available only to steps in that job.
  • Step-level: declared inside a step; overrides job/workflow variables with the same name.
Precedence: step-level > job-level > workflow-level.
Step-level env entries override job-level and workflow-level variables with the same name. Use step-level for single-step overrides and job-level for values shared by multiple steps in the same job.

Job-level environment variables

Define job-level variables under jobs.<job_id>.env. These values are available to all steps in that job only. Example: a job-level env for tests and build steps.
Because these variables are under job1.env, they are not visible to other jobs (e.g., job2) unless you duplicate or lift them to workflow-level.

Why missing env vars cause runtime errors

If your application reads required configuration from environment variables (for example, a Pydantic Settings class), missing values can cause immediate initialization failures. Typical CI error output from Pydantic looks like this:
This indicates the runner could not find the expected env vars when importing your application code. To avoid this, ensure required env vars are provided by the workflow (or mocked/stubbed during tests).

Step-level environment variables

Use env inside a specific step to provide values only that step needs. Step-level vars override those at job or workflow scope when names collide. Example: job-level env with a conditional step that uses step-level overrides. Note the GitHub Actions expression is shown in the YAML:

Built-in environment variables

Runners expose many built-in environment variables such as GITHUB_REPOSITORY, GITHUB_RUN_NUMBER, and GITHUB_SHA. Use these when you need metadata about the current workflow run. See the GitHub Docs for the full list:
A screenshot of the GitHub Docs website showing a reference table of GitHub Actions environment variables and their descriptions. The page includes a left navigation menu and browser tabs visible across the top.

Workflow-level environment variables (shared across jobs)

If many jobs require the same variables, define them at the workflow root (one level above jobs). These variables propagate to all jobs and steps unless overridden. Example:

Common workflow mistakes

  • Leaving a job with no steps will cause the workflow parser to reject the file. Example error:
  • Expecting job1.env to be available in job2—job-level variables are not shared.

Runner logs and environment validation

When a runner executes a step, it often prints an environment dump that shows the env variables available to that step. This is helpful for debugging and confirming that variables are set. For example:
Note: GitHub Actions automatically masks secrets in logs. If you use GitHub Secrets, their values will be redacted and not shown in plain text.

Note about testing databases in CI

If your tests rely on a database at localhost, you must either start a database service on the runner or connect to an external test database. GitHub Actions supports service containers (for example, the official postgres service) to spin up databases during the job: If the runner lacks a database, DB libraries (e.g., SQLAlchemy) will raise connection errors during setup:

Best practices for secrets

Never hard-code sensitive values (production credentials, private keys, tokens) in your repository or workflow YAML. Instead, store them in GitHub Secrets and reference them via the secrets context. Example:
Never store sensitive credentials in plaintext in your repository. Use GitHub Secrets (or another secrets manager) and reference them via the secrets context so the values are kept private and not visible in your repo.

Quick reference table

Summary

  • Use workflow-level env for values shared across jobs.
  • Use job-level env for values used by multiple steps within the same job.
  • Use step-level env for single-step values and to override broader scopes.
  • Always use secrets for sensitive data and never commit credentials to the repo.
  • Ensure CI services (like databases) are available to the runner when tests expect them.

Watch Video