Pipelines need configuration: API keys, deployment targets, feature flags. But NOT all configuration is equal — some is sensitive. This lesson teaches you GitHub's configuration hierarchy and how to use it safely.
The Configuration Hierarchy
GitHub Actions provides three levels of configuration, each with different visibility and security:
Secrets: Handling Sensitive Data
Setting Secrets
Repository → Settings → Secrets and variables → Actions → New repository secret
Hardcode secrets in YAML:
run: curl -H "Token: abc123secret"
Visible in git history FOREVER
Use secrets context:
run: curl -H "Token: ${{ secrets.API_TOKEN }}"
Encrypted, masked in logs, never exposed
How Secrets Appear in Logs
echo a secret to a file, embed it in a URL, or pass it to an untrusted action. If you accidentally expose one, rotate immediately.
Variables: Non-Sensitive Configuration
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to ${{ vars.ENVIRONMENT }}
run: |
echo "Deploying to ${{ vars.APP_NAME }}"
echo "Region: ${{ vars.AZURE_REGION }}"
echo "Cluster: ${{ vars.CLUSTER_NAME }}"
Variables are for things like app names, regions, cluster names — anything you want configurable but NOT secret.
Environments: Per-Stage Configuration + Approval Gates
Environments are the most powerful configuration mechanism. They combine secrets + variables + deployment controls.
Using Environments in YAML
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.myapp.com # Shown in GitHub UI
steps:
- run: echo "DB: ${{ secrets.DB_URL }}"
# ↑ Gets the STAGING-specific DB_URL secret!
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production # ← This triggers the approval gate!
url: https://myapp.com
steps:
- run: echo "DB: ${{ secrets.DB_URL }}"
# ↑ Gets the PRODUCTION-specific DB_URL secret!
🏋️ Exercise: Set Up Environments
Step 1: Create environments in GitHub
- Go to repo → Settings → Environments
- Create "staging" — no protection rules
- Create "production" — add yourself as required reviewer
- Add a variable
DEPLOY_TARGETto each (value: "staging-server" / "production-server")
Step 2: Create a deployment workflow
# .github/workflows/deploy.yml
name: Deploy
on:
workflow_dispatch: # Manual trigger for now
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- run: echo "🚀 Deploying to ${{ vars.DEPLOY_TARGET }}"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- run: echo "🚀 Deploying to ${{ vars.DEPLOY_TARGET }}"
Step 3: Trigger and observe
- Go to Actions → Deploy → Run workflow
- Watch staging deploy automatically
- See production pause and wait for your approval
- Click "Review deployments" → Approve → production deploys
🎉 You just built a gated deployment pipeline!
The env: Keyword (Workflow-Level)
For values that aren't secrets or GitHub-managed variables, use inline env::
# Workflow-level (available to all jobs)
env:
NODE_ENV: production
IMAGE_NAME: my-app
jobs:
build:
# Job-level (only this job)
env:
CI: true
steps:
- name: Use env vars
# Step-level (only this step)
env:
API_KEY: ${{ secrets.API_KEY }}
run: echo "$NODE_ENV $IMAGE_NAME $CI"
Decision Framework: What Goes Where?
| Data | Where to Store | Why |
|---|---|---|
| Azure client ID | Secret | Auth credential |
| Database password | Environment secret | Different per env |
| App name | Variable | Not sensitive, changes rarely |
| Cluster name per env | Environment variable | Different per env, not secret |
| NODE_ENV=production | Inline env: | Static, not configurable |
| Image registry URL | Variable | Not sensitive, shared across jobs |
🧠 Recall Check
- What's the syntax difference between accessing a secret and a variable?
- Can you read a secret's value after setting it in the GitHub UI?
- How do you make a deployment job require human approval?
- If staging and production both have a secret called
DB_URL, how does the workflow know which to use?
Reveal answers
${{ secrets.NAME }}vs${{ vars.NAME }}- No. Secrets are write-only from the UI. Once saved, you can only update or delete them, never view the value.
- Add
environment: productionto the job, and configure the "production" environment with required reviewers in Settings → Environments. - The
environment:key on the job determines which environment's secrets are injected.environment: staging→ staging's DB_URL.environment: production→ production's DB_URL.
Next lesson: Artifacts & Job Outputs — passing files and data between jobs.