GitHub Actions Cheatsheet

Secrets and Environment

Use this GitHub Actions reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Secret Types

TypeScopeSet in
Repository secretOne repoRepo → Settings → Secrets and variables → Actions
Organization secretSelected/all repos in orgOrg → Settings → Secrets and variables → Actions
Environment secretOne deployment environmentRepo → Settings → Environments → <env> → Secrets
GITHUB_TOKENCurrent repo, current runAuto-provisioned; no setup needed

Using Secrets

steps:
  - run: ./deploy.sh
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DB_URL: ${{ secrets.DATABASE_URL }}

  # GITHUB_TOKEN is always available
  - run: gh pr comment --body "Done"
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Secrets are never printed in logs — they appear as ***. Never echo a secret directly; pass via environment variables.

GITHUB_TOKEN — Default Permissions

PermissionDefault
contentsread (write on default branch push)
packagesread
pull-requestsread
issuesread
id-tokennone
deploymentsread
pagesnone

Override at workflow or job level:

permissions:
  contents: write
  pull-requests: write
  issues: write

Variables (Non-Secret Configuration)

Unencrypted key-value pairs visible in logs:

# Access repo/org variables
- run: echo "APP_ENV=${{ vars.APP_ENV }}"
- run: echo "API_URL=${{ vars.API_URL }}"

Set via: Repo → Settings → Secrets and variables → Actions → Variables tab.

Scope matches secrets: repository, organization, environment.

Environment-Level Secrets

jobs:
  deploy:
    environment: production       # gates on required reviewers, rules
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}    # from the 'production' environment

OIDC — Keyless Cloud Auth

Eliminates long-lived cloud credentials. The runner gets a short-lived OIDC token that cloud providers verify.

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
          aws-region: us-east-1
          # No static AWS_ACCESS_KEY_ID needed

      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/123/locations/global/workloadIdentityPools/...
          service_account: ci@my-project.iam.gserviceaccount.com

      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

Setting Environment Variables

# In workflow/job/step env: key
env:
  NODE_ENV: production
  API_URL: https://api.example.com

# From a step (available to later steps in the same job)
- run: echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV

# From a secret
- run: npm run deploy
  env:
    TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Variable Precedence (highest → lowest)

  1. Step env:
  2. Job env:
  3. Workflow env:
  4. $GITHUB_ENV (set by prior step)
  5. Runner environment defaults

Masking Values in Logs

- run: echo "::add-mask::$MY_SECRET"

Or in JS:

core.setSecret(value);

Any subsequent occurrence of that string in logs is replaced with ***.

Encrypted Secrets via CLI

# Set a repo secret
gh secret set MY_SECRET --body "my-value"
gh secret set MY_SECRET < secret.txt

# From environment variable
gh secret set MY_SECRET --env-file .env

# Set at org level
gh secret set MY_SECRET --org my-org --visibility all

# Set for a specific environment
gh secret set MY_SECRET --env production

# List secrets (names only, not values)
gh secret list
gh secret list --env production

# Delete a secret
gh secret delete MY_SECRET

Variables via CLI

# Set a variable
gh variable set APP_ENV --body "staging"
gh variable set APP_ENV --org my-org

# List variables
gh variable list

# Delete a variable
gh variable delete APP_ENV

Common Patterns

# NPM publish with token
- run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

# SSH deployment
- run: |
    eval "$(ssh-agent -s)"
    ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}"
    rsync -avz dist/ user@host:/var/www/

# Docker registry login
- uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKER_USERNAME }}
    password: ${{ secrets.DOCKER_PASSWORD }}

# GitHub Packages (GHCR) — no secret needed
- uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

GITHUB_TOKEN Scope Summary

# Minimal — read-only for most things
permissions: read-all

# Common CI pattern
permissions:
  contents: read
  checks: write          # annotate test results
  pull-requests: write   # post comments

# Release workflow
permissions:
  contents: write        # create releases, push tags
  packages: write        # push to GHCR

# Pages deploy
permissions:
  contents: read
  pages: write
  id-token: write