GitHub Actions: CI/CD Workflows From First Run to Secure Deployments · GitHub Actions Workflows: Triggers, CI and Matrices
Variables, Secrets, Expressions and Outputs
Configure workflows with env, vars and secrets, use ${{ }} expressions and if conditions, and pass values between steps.
What you'll build
A workflow that uses a repository variable, a secret, a conditional step, and a value passed from one step to another through GITHUB_OUTPUT.
The idea
env:sets environment variables at the workflow, job or step level.vars.NAMEreads a configuration variable (plain text, visible) set in repository or organization settings.secrets.NAMEreads an encrypted secret. Its value is masked in logs, and it is not available to workflows from forks.- Expressions
${{ ... }}read contexts (github,env,vars,secrets,matrix,needs,steps,runner) and call functions likecontains(),startsWith(),format()andhashFiles(). if:decides whether a step or job runs. Use status functionssuccess(),failure()andalways().- Step outputs: write
name=valueto the file$GITHUB_OUTPUT, give the step anid, then read${{ steps.<id>.outputs.name }}later.
The code
name: Config demo
on: push
env:
APP_NAME: shop
jobs:
demo:
runs-on: ubuntu-latest
env:
REGION: ${{ vars.REGION || 'eu-west-1' }}
steps:
- name: Compute a version
id: ver
run: echo "version=1.0.${GITHUB_RUN_NUMBER}" >> "$GITHUB_OUTPUT"
- name: Use the output
run: echo "Building $APP_NAME ${{ steps.ver.outputs.version }} for $REGION"
- name: Call an API with a secret
env:
API_KEY: ${{ secrets.API_KEY }}
run: >-
curl --fail -H "Authorization: Bearer $API_KEY"
https://api.example.com/health
- name: Only on the main branch
if: github.ref == 'refs/heads/main'
run: echo "On main"
- name: Always report
if: ${{ always() }}
run: echo "Job finished"
Output
actionlint: no problems found workflow: Config demo triggers: push jobs, in the order they run: demo: runs on ubuntu-latest | 5 step(s)
Run this on your computer — see “Get Started” for setup.
The secret goes through an environment variable, never straight into the script text; that matters for security (see the last module).
Common mistakes
- Putting secrets in
vars. Variables are not masked. Anything sensitive belongs insecrets. - Missing secret = empty string. A mistyped secret name doesn't fail; it silently becomes empty. Check the secret exists.
- Using
$GITHUB_ENVand expecting the value in the same step. Variables written toGITHUB_ENVare available to later steps only. - Quoting problems in
if:. Write string literals with single quotes inside expressions:github.ref == 'refs/heads/main'.
Key points
env,varsandsecretsprovide configuration; secrets are masked.${{ }}expressions read contexts;if:gates steps and jobs.- Share values between steps with
$GITHUB_OUTPUTand stepids. Further reading: GitHub Docs.
Practice questions
How does a step pass a computed value to a later step?
Answer
It appends name=value to the file named by $GITHUB_OUTPUT, has an id, and the later step reads ${{ steps.<id>.outputs.name }}.
What happens if you reference a secret that doesn't exist?
Answer
No error: the expression evaluates to an empty string. You should verify the secret is set, or fail the step if the value is empty.
Open in the app to mark lessons complete, take notes and track your progress.