š§Ŗ GitHub Actions and Environment Variables: Static vs. Dynamic Smackdown
Letās talk about environment variables in GitHub Actions ā those little gremlins that either make your CI/CD run silky smooth or throw a wrench in your perfectly crafted YAML.
If you’ve ever squinted at your pipeline and wondered, āWhere the heck should I declare this ANSIBLE_CONFIG thing so it doesnāt vanish into the void between steps?ā, youāre not alone. Iāve been there. Iāve screamed at $GITHUB_ENV. Iāve misused export. Iāve over-engineered echo. But fear not, dear reader ā Iāve distilled it down so you donāt have to.
In this post, weāll look at the right ways (and a few less right ways) to set environment variables ā and more importantly, when to use static vs dynamic approaches.
š§ Static Variables: Set It and Forget It
Got a variable like ANSIBLE_STDOUT_CALLBACK=yaml thatās the same every time? Congratulations, youāve got yourself a static variable! These are the boring, predictable, low-maintenance types that make your CI life a dream.
ā
Best Practice: Job-Level env
If your variable is static and used across multiple steps, this is the cleanest, classiest, and least shouty way to do it:
jobs:
my-job:
runs-on: ubuntu-latest
env:
ANSIBLE_CONFIG: ansible.cfg
ANSIBLE_STDOUT_CALLBACK: yaml
steps:
- name: Use env vars
run: echo "ANSIBLE_CONFIG is $ANSIBLE_CONFIG"
Why it rocks:
- š Super readable
- š¦ Available in every step of the job
- š§¼ Keeps your YAML clean ā no extra echo commands, no nonsense
Unless you have a very specific reason not to, this should be your default.
š© Dynamic Variables: Born to Be Wild
Now what if your variables arenāt so chill? Maybe you calculate something in one step and need to pass it to another ā a file path, a version number, an API token from a secret backend ritual…
Thatās when you reach for the slightly more⦠creative option:
š§ $GITHUB_ENV to the rescue
- name: Set dynamic environment vars
run: |
echo "BUILD_DATE=$(date +%F)" >> $GITHUB_ENV
echo "RELEASE_TAG=v1.$(date +%s)" >> $GITHUB_ENV
- name: Use them later
run: echo "Tag: $RELEASE_TAG built on $BUILD_DATE"
What it does:
- Persists the variables across steps
- Works well when values are calculated during the run
- Makes you feel powerful
šŖ Fancy Bonus: Heredoc Style
If you like your YAML with a side of Bash wizardry:
- name: Set vars with heredoc
run: |
cat <<EOF >> $GITHUB_ENV
FOO=bar
BAZ=qux
EOF
Because sometimes, you just want to feel fancy.
šµāš« What Not to Do (Unless You Really Mean It)
- name: Set env with export
run: |
export FOO=bar
echo "FOO is $FOO"
This only works within that step. The minute your pipeline moves on, FOO is gone. Poof. Into the void. If thatās what you want, fine. If not, donāt say I didnāt warn you.
š§ TL;DR ā The Cheat Sheet
| Scenario | Best Method |
|---|---|
| Static variable used in all steps | env at the job level ā
|
| Static variable used in one step | env at the step level |
| Dynamic value needed across steps | $GITHUB_ENV ā
|
| Dynamic value only needed in one step | export (but donāt overdo it) |
| Need to show off with Bash skills | cat <<EOF >> $GITHUB_ENV š |
š§Ŗ My Use Case: Ansible FTW
In my setup, I wanted to use:
ANSIBLE_CONFIG=ansible.cfg
ANSIBLE_STDOUT_CALLBACK=yaml
These are rock-solid, boringly consistent values. So instead of writing this in every step:
- name: Set env
run: |
echo "ANSIBLE_CONFIG=ansible.cfg" >> $GITHUB_ENV
I now do this:
jobs:
deploy:
runs-on: ubuntu-latest
env:
ANSIBLE_CONFIG: ansible.cfg
ANSIBLE_STDOUT_CALLBACK: yaml
steps:
...
Cleaner. Simpler. One less thing to trip over when Iām debugging at 2am.
š¬ Final Thoughts
Environment variables in GitHub Actions arenāt hard ā once you know the rules of the game. Use env for the boring stuff. Use $GITHUB_ENV when you need a little dynamism. And remember: if youāre writing export in step after step, something probably smells.
Got questions? Did I miss a clever trick? Want to tell me my heredoc formatting is ugly? Hit me up in the comments or toot at me on Mastodon.
āļø Posted by Amedee, who loves YAML almost as much as dancing polskas.
š„ Because good CI is like a good dance: smooth, elegant, and nobody falls flat on their face.
š» Scheduled to go live on 20 August ā just as Boombalfestival kicks off. Because why not celebrate great workflows and great dances at the same time?

