Streamline your GitHub Actions workflows by reducing duplication and centralizing configuration with environment variables. In this guide, you’ll learn to declare variables at the step , job , and workflow levels, so you can:
Maintain clean, DRY workflows
Easily update container names, registry endpoints, and other parameters
Secure sensitive data using GitHub Secrets
Below is a sample workflow file (variable-secrets.yaml) that builds, pushes, and deploys a Docker image:
name : Exploring Variables and Secrets
on :
push :
jobs :
docker :
runs-on : ubuntu-latest
steps :
- name : Docker Build
run : docker build -t docker.io/dockerUsername/imageName:latest
- name : Docker Login
run : docker login --username=dockerUsername --password=s3CuRePa$$w0rd
- name : Docker Publish
run : docker push docker.io/dockerUsername/imageName:latest
deploy :
needs : docker
runs-on : ubuntu-latest
steps :
- name : Docker Run
run : docker run -d -p 8080:80 docker.io/dockerUsername/imageName:latest
Whenever you push:
GitHub Actions builds the Docker image.
Logs in to Docker Hub.
Pushes the image.
Runs the container in the deploy job, which depends on docker.
Notice how the registry, username, and image name are repeated. Let’s eliminate this duplication using environment variables.
Overview of Environment Variable Scopes
Level Declaration Location Scope Step-level Inside a specific step Only that step Job-level Under jobs.<job_id>.env All steps within that job Workflow-level Top of the YAML file Every job and step in the workflow
1. Step-Level Environment Variables
Define variables in an individual step. This is ideal for values that aren’t reused elsewhere.
jobs :
docker :
runs-on : ubuntu-latest
steps :
- name : Docker Build
env :
CONTAINER_REGISTRY : docker.io
DOCKER_USERNAME : siddharth1
IMAGE_NAME : github-actions-nginx
run : |
docker build -t $CONTAINER_REGISTRY/$DOCKER_USERNAME/$IMAGE_NAME:latest
- name : Docker Login
env :
DOCKER_USERNAME : siddharth1
DOCKER_PASSWORD : ${{ secrets.DOCKER_PASSWORD }}
run : |
docker login --username=${DOCKER_USERNAME} \
--password=${DOCKER_PASSWORD}
- name : Docker Publish
env :
CONTAINER_REGISTRY : docker.io
DOCKER_USERNAME : siddharth1
IMAGE_NAME : github-actions-nginx
run : |
docker push $CONTAINER_REGISTRY/$DOCKER_USERNAME/$IMAGE_NAME:latest
deploy :
needs : docker
runs-on : ubuntu-latest
steps :
- name : Docker Run
env :
CONTAINER_REGISTRY : docker.io
DOCKER_USERNAME : siddharth1
IMAGE_NAME : github-actions-nginx
run : |
docker run -d -p 8080:80 \
${{ env.CONTAINER_REGISTRY }}/${{ env.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:latest
You can reference an environment variable using:
$VAR_NAME
${{ env.VAR_NAME }} (recommended when mixing with expressions)
2. Job-Level Environment Variables
Apply the same variables to all steps in a job by declaring them under jobs.<job_id>.env:
jobs :
docker :
runs-on : ubuntu-latest
env :
CONTAINER_REGISTRY : docker.io
DOCKER_USERNAME : siddharth1
IMAGE_NAME : github-actions-nginx
steps :
- name : Docker Build
run : |
docker build -t $CONTAINER_REGISTRY/$DOCKER_USERNAME/$IMAGE_NAME:latest
- name : Docker Login
env :
DOCKER_PASSWORD : ${{ secrets.DOCKER_PASSWORD }}
run : |
docker login --username=${DOCKER_USERNAME} \
--password=${DOCKER_PASSWORD}
- name : Docker Publish
run : |
docker push $CONTAINER_REGISTRY/$DOCKER_USERNAME/$IMAGE_NAME:latest
deploy :
needs : docker
runs-on : ubuntu-latest
env :
CONTAINER_REGISTRY : docker.io
DOCKER_USERNAME : siddharth1
IMAGE_NAME : github-actions-nginx
steps :
- name : Docker Run
run : |
docker run -d -p 8080:80 \
$CONTAINER_REGISTRY/$DOCKER_USERNAME/$IMAGE_NAME:latest
Any env under a job cascades to every step. You can still override or augment variables at the step level.
3. Workflow-Level Environment Variables
Declare variables at the top of your workflow to make them available across all jobs and steps:
name : Exploring Variables and Secrets
on :
push :
env :
CONTAINER_REGISTRY : docker.io
DOCKER_USERNAME : siddharth1
IMAGE_NAME : github-actions-nginx
jobs :
docker :
runs-on : ubuntu-latest
steps :
- name : Docker Build
run : |
docker build -t ${{ env.CONTAINER_REGISTRY }}/${{ env.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:latest
- name : Docker Login
env :
DOCKER_PASSWORD : ${{ secrets.DOCKER_PASSWORD }}
run : |
docker login --username=${DOCKER_USERNAME} \
--password=${DOCKER_PASSWORD}
- name : Docker Publish
run : |
docker push $CONTAINER_REGISTRY/$DOCKER_USERNAME/$IMAGE_NAME:latest
deploy :
needs : docker
runs-on : ubuntu-latest
steps :
- name : Docker Run
run : |
docker run -d -p 8080:80 \
$CONTAINER_REGISTRY/$DOCKER_USERNAME/$IMAGE_NAME:latest
Never hard-code sensitive values like passwords or API keys. Store them in GitHub Secrets and reference them with ${{ secrets.YOUR_SECRET_NAME }}.
Verifying Your Workflow
Commit and push your changes.
Open the Actions tab in your repository.
Watch the docker and deploy jobs run sequentially.
Expand each step to confirm that variables are correctly substituted and that secrets remain masked.
Use ${{ env.VAR_NAME }} when combining expressions with literal strings to ensure consistent parsing.
References