TL;DR
- Secrets are encrypted at rest and injected as environment variables into your workflow runners.
- Reference them with
${{ secrets.SECRET_NAME }}— they are automatically masked in logs. - Repo secrets are available to all workflows; environment secrets add an approval gate.
Adding a secret in the GitHub UI
- Go to your repository → Settings → Secrets and variables → Actions.
- Click New repository secret.
- Enter a name (e.g.
DATABASE_URL) and paste the value. - Click Add secret. The value is write-only — you cannot read it back.
To update a secret, click its name and enter a new value. The old value is overwritten immediately and all future workflow runs use the new value.
Using secrets in a workflow
Reference any secret with the ${{ secrets.NAME }} expression. GitHub injects it as an environment variable:
name: Deploy
on: push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Run deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
run: npm run deployYou can also pass secrets directly to run steps via env: at the job level, or use them in with: inputs for actions.
Automatic masking
GitHub scans every log line and replaces any occurrence of a secret's value with ***. This works even if the secret is printed accidentally. However, masking is not foolproof — base64-encoded or URL-encoded versions of the secret will not be masked. Never deliberately print secrets in logs.
Repo secrets vs environment secrets
GitHub has two scopes for secrets:
- Repository secrets — available to all workflows in the repo. Good for CI credentials, test API keys, and deployment tokens that every PR branch needs.
- Environment secrets — scoped to a named environment (e.g.
production). Can require manual approval from a reviewer before the job runs. Use these for production deploy keys and live API credentials.
To use an environment secret, add environment: production to your job:
jobs:
deploy-prod:
runs-on: ubuntu-latest
environment: production # triggers approval gate
steps:
- run: npm run deploy
env:
PROD_API_KEY: ${{ secrets.PROD_API_KEY }}Organisation secrets
If you manage multiple repos, organisation secrets let you define a secret once and share it across selected repos or all repos in the org. Go toOrganisation settings → Secrets and variables → Actions.
Avoid common mistakes
- Never hardcode secrets in workflow files. Even private repos get forked; secrets in YAML travel with the fork.
- Rotate on leak. If a secret appears in a log, rotate it immediately — GitHub's masking does not retroactively redact already-stored logs.
- Use OIDC for cloud credentials. Instead of storing AWS or GCP keys as secrets, configure OpenID Connect so GitHub authenticates directly to your cloud provider with short-lived tokens. No long-lived keys to rotate.
- Limit secret access on PRs from forks. Forked PRs do not receive secrets by default — this is correct behaviour. Never change the "Fork pull request workflows" setting to allow secrets on fork PRs.
Adding secrets via the CLI
# Set a secret
gh secret set DATABASE_URL --body "postgres://..." --repo owner/repo
# Set from a file
gh secret set PRIVATE_KEY < private.pem
# List secrets (names only — values are never shown)
gh secret listFAQ
Can Dependabot read my repository secrets?
No — by default, Dependabot pull requests run with a separate, more restricted secret store (Dependabot secrets, configured separately under Settings → Secrets and variables → Dependabot). This is intentional: it stops a malicious dependency update from exfiltrating your deploy keys.
Do secrets survive a repository rename or transfer?
Yes — secrets are tied to the repository's internal ID, not its name or owner, so renaming a repo or transferring it to another organisation keeps all repo and environment secrets intact.
Can I share one secret across many repositories without copying it into each?
Yes — that's what organisation secrets are for. Define the secret once at the org level and choose which repositories can access it, instead of pasting the same value into every repo's settings.
What happens to a secret if the workflow that used it is deleted?
Nothing — secrets live at the repository/environment/organisation level, independent of any specific workflow file. Deleting or renaming a .yml workflow has no effect on stored secrets.
Pulling secrets locally for development
GitHub Actions secrets are not accessible outside CI. For local development, use a .env.local file (gitignored) or a tool like vercel env pull / Doppler CLI to sync secrets to your machine. Never copy production secrets into a .env file that could be committed.