GitHub Actions Tutorial — Secrets

2 minute read

Secrets

Secrets store sensitive data — API keys, tokens, passwords — encrypted and accessible only during workflow execution. They are never printed in logs.


Creating Secrets

Repository secrets: Settings → Secrets and variables → Actions → New repository secret

Organization secrets: Organization Settings → Secrets → New organization secret (can be scoped to specific repos)

Environment secrets: Settings → Environments → Select environment → Add secret

Secret Hierarchy

Level Scope Defined In
Environment One environment only Repo → Environments
Repository All workflows in repo Repo → Settings
Organization All repos (or selected repos) Org → Settings

If the same secret name exists at multiple levels: Environment > Repository > Organization.


Using Secrets in Workflows

Access secrets with the secrets context:


steps:
  - name: Deploy
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
    run: |
      echo "Deploying with API key..."
      ./deploy.sh

Important Rules

  1. Secrets are NOT available in if: conditions directly — assign to env first
  2. Secrets are automatically masked in logs — if the value appears in output, it shows ***
  3. Secrets can’t be used at workflow-level env: — use job or step level
  4. Fork PRs don’t have access to secrets (security measure)

Secret Masking

GitHub automatically masks secret values in logs:


steps:
  - name: This won't expose the secret
    env:
      TOKEN: ${{ secrets.MY_TOKEN }}
    run: echo "Token is $TOKEN"
    # Output: Token is ***

Structured Secret Masking

If your secret is a JSON object, individual values may NOT be masked. Use environment variables for each field:


steps:
  - name: Safe approach
    env:
      DB_HOST: ${{ secrets.DB_HOST }}
      DB_PASS: ${{ secrets.DB_PASS }}
    run: ./connect-db.sh


Secrets with Docker

Pass secrets to Docker build steps:


steps:
  - uses: docker/build-push-action@v6
    with:
      context: .
      push: true
      tags: myorg/myapp:latest
      build-args: |
        NPM_TOKEN=${{ secrets.NPM_TOKEN }}

Warning: Build args are visible in the image history. For true build secrets, use Docker BuildKit secrets:


steps:
  - uses: docker/build-push-action@v6
    with:
      context: .
      push: true
      tags: myorg/myapp:latest
      secrets: |
        "npm_token=${{ secrets.NPM_TOKEN }}"


Limits

Limit Value
Secret name max length 255 characters
Secret value max size 48 KB
Max secrets per repo 100
Max secrets per org 1,000
Max secrets per environment 100

For values larger than 48 KB, store the encrypted file in your repo and use a passphrase secret to decrypt it.


Common Mistakes

Mistake Fix
Secret value shows *** but is empty Secret name is case-sensitive; check exact name in Settings
Secret not available in fork PR Expected behavior — use pull_request_target if needed (carefully)
Secret in workflow-level env: Move to job-level or step-level env:
Committing secrets to repo Use .gitignore, git-secrets, or GitHub’s secret scanning

Quick Reference

Concept Syntax
Access secret ${{ secrets.NAME }}
In env var env: KEY: ${{ secrets.NAME }}
Environment secret ${{ secrets.NAME }} (with environment:)
Masked in logs Automatic for all secrets
Max size 48 KB per secret

← Previous: Environment Variables

Next: GITHUB_TOKEN →

Updated: