Skip to content

Secrets Explained


Everything you’ve passed between steps and jobs so far has been plain text — fine for version numbers and commit SHAs, completely wrong for an AWS access key or a database password. GitHub Secrets exist specifically for that category of value: encrypted at rest, masked in logs, and never visible in the workflow file itself. Where you store a secret — repository, environment, or organization level — determines who can use it and how tightly you can control access.

Why Secrets Aren’t Just Regular Variables

A regular env value sits in plain text in your YAML file, visible to anyone who can read the repository. A secret is stored separately, referenced by name, and GitHub actively scrubs its value from any log output — even accidentally. If you echo a secret directly, GitHub replaces it with *** in the Actions log automatically, without you doing anything special.

Repository Secrets

The most common tier — a secret scoped to a single repository, set through Settings → Secrets and variables → Actions → New repository secret in the GitHub web UI.

name: Repo Secret Demo
on: workflow_dispatch

jobs:
  demo:
    runs-on: ubuntu-latest
    steps:
      - run: |
          if [ -n "${{ secrets.DEMO_TOKEN }}" ]; then
            echo "Secret is set and non-empty"
          else
            echo "Secret is missing"
          fi

To try this, create a repository on GitHub, add a repository secret named DEMO_TOKEN with any placeholder value through the Settings UI, then push this workflow:

mkdir gha-secrets-demo && cd gha-secrets-demo
git init
mkdir -p .github/workflows
cat > .github/workflows/repo-secret.yml << 'EOF'
name: Repo Secret Demo
on: workflow_dispatch

jobs:
  demo:
    runs-on: ubuntu-latest
    steps:
      - run: |
          if [ -n "${{ secrets.DEMO_TOKEN }}" ]; then
            echo "Secret is set and non-empty"
          else
            echo "Secret is missing"
          fi
EOF
git add .
git commit -m "Add repo secret demo workflow"
git branch -M main
git push -u origin main

Trigger it manually — with the secret set, you’ll see “Secret is set and non-empty.” Notably, even this presence check never prints the actual value.

Environment Secrets

GitHub Environments (a separate concept from env: variables — configured under Settings → Environments) can hold their own secrets, scoped to that environment specifically. A job only gets access to an environment’s secrets if it explicitly targets that environment.

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - run: echo "Deploying with production credentials"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}

The environment: production line here does two things: it scopes secret access to whatever’s defined on the production environment (falling back to repository secrets if no environment-level secret with that name exists), and it activates any protection rules configured on that environment — like requiring a specific reviewer to approve before the job proceeds.

Note

Environment secrets exist precisely to solve the problem of having different AWS credentials for staging versus production, both named AWS_ACCESS_KEY_ID in your workflow file, resolving to different actual values depending on which environment the job targets. Without this, you’d need separate secret names like STAGING_AWS_KEY and PROD_AWS_KEY and manually pick the right one everywhere — environments handle that indirection for you.

Organization Secrets

For anyone managing multiple repositories under a GitHub organization, organization-level secrets let you define a value once and share it across some or all repositories in that org, rather than copy-pasting the same secret into every repository’s settings individually. This tier is configured at the organization level, outside any single repository, and typically requires org-owner permissions to manage.

Since this course focuses on individual repositories, you won’t set one up hands-on here — but it’s worth knowing this tier exists, because in any real company with more than a handful of repositories, organization secrets are how a shared credential (a company-wide package registry token, for instance) gets distributed without duplicating it dozens of times.

Secret Resolution Order

When a workflow references secrets.SOME_NAME, GitHub resolves it in this order: environment secret (if the job targets an environment and that environment defines it) takes priority, then repository secret, then organization secret. This lets a repository or environment override an organization-wide default without needing to change the organization-level value for everyone else.

Comparing The Three Tiers

TierScopeSet WhereTypical Use
RepositoryOne repositoryRepo Settings → SecretsRepo-specific credentials
EnvironmentOne environment within a repoRepo Settings → EnvironmentsStaging vs. production separation, approval gates
OrganizationMultiple repos across an orgOrg Settings → SecretsShared credentials across many repos

How Real DevOps Engineers Use This

Repository secrets are the default starting point for any project — API keys, deploy tokens, anything specific to that one codebase. Environment secrets become essential the moment a pipeline needs to distinguish staging from production, since it lets the exact same workflow file deploy safely to both, picking up different credentials automatically based on which environment a job targets, with no branching logic needed in the YAML itself. Organization secrets matter most at scale — a platform team managing dozens of microservice repositories will typically push shared infrastructure credentials (a container registry login, a shared monitoring API key) down as organization secrets once, rather than manage the same value in forty different places and inevitably let some of them drift out of sync.

A genuinely important habit: environment secrets combined with required reviewers on the production environment is one of the simplest, most effective guardrails against an accidental or malicious production deploy — the workflow literally cannot proceed without a human clicking approve, regardless of what triggered it.

Clean up:

cd .. && rm -rf gha-secrets-demo

Remember to also delete the DEMO_TOKEN repository secret from GitHub’s Settings UI if you don’t want it lingering in an old demo repo.

Last updated on