How to set up secrets management in GitHub Actions (the right way)
GitHub Actions is where a lot of secrets management problems come to a head. You need your database URL to run tests, your API keys to deploy, your credentials to build Docker images — and none of them should be hardcoded in your workflow files.
Here’s a practical guide to doing it right, from the basics to more robust approaches.
The wrong way — and why people do it
The most common mistake is hardcoding credentials directly in workflow files:
# Don't do this
- name: Deploy
run: |
DATABASE_URL=postgres://user:password@host/db node deploy.js
This is obviously bad — your credentials are in your git history forever. But people do it because it works and they’re moving fast.
A slightly less bad but still problematic pattern is committing a
.env file and reading from it in the workflow:
- name: Deploy
run: |
export $(cat .env | xargs)
node deploy.js
This is still terrible. Your .env file is now in your repository
and everyone who can read the repo can read your secrets.
The GitHub native approach — repository secrets
GitHub’s built-in secrets are the baseline. Go to your repository settings, find Secrets and Variables under Security, and add your secrets there.
Then reference them in your workflow:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy
run: node deploy.js
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
STRIPE_SECRET: ${{ secrets.STRIPE_SECRET }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
This is significantly better than hardcoding. Secrets are encrypted at rest, masked in logs, and not exposed to forked pull requests by default.
The problems start when you have many secrets across multiple environments:
- You have 20 variables for production and 20 different ones for staging — that’s 40 secrets to manage in the GitHub UI
- When a value changes you need to update it in GitHub manually
- There’s no audit log of who changed what and when
- New team members need to be given access to the right secrets somehow
- If you have multiple repositories, you’re managing secrets in multiple places
GitHub’s environment secrets help with the environment problem, but you’re still managing everything manually through the UI.
Using GitHub environments
GitHub environments let you group secrets by deployment target.
Create environments called production and staging in your
repository settings, add environment-specific secrets to each,
and reference them in your workflow:
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # references production environment secrets
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: node deploy.js
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
You can also add protection rules to environments — require a reviewer to approve production deployments, restrict which branches can deploy to production, and set deployment timeouts.
This is the right GitHub-native approach for most teams. The main limitation is still the manual management — when credentials rotate you’re updating them in the GitHub UI one by one.
The centralized approach — using a secrets manager
For teams with many secrets, multiple repositories, or frequent rotation, a centralized secrets manager is worth adding. The idea is that your secrets live in one place and your CI/CD pipeline fetches them at runtime rather than storing them as repository secrets.
With EnvMaster this looks like:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install EnvMaster CLI
run: curl -fsSL https://raw.githubusercontent.com/Atlantis-Services/envmaster-cli/master/install.sh | sh
- name: Deploy with injected variables
run: envmaster run -- node deploy.js
env:
ENVMASTER_TOKEN: ${{ secrets.ENVMASTER_TOKEN }}
You only need one secret in GitHub — ENVMASTER_TOKEN. Everything
else — database URLs, API keys, third-party credentials — lives in
EnvMaster and gets injected automatically when the workflow runs.
When a value changes you update it once in EnvMaster and every workflow that uses it picks up the change automatically. No manual updates in GitHub, no secrets getting out of sync across repositories.
Comparing the approaches
Repository secrets alone works well for small projects with few secrets and infrequent rotation. Simple to set up, no additional tools required.
Repository secrets with environments is the right baseline for most teams. Adds proper environment separation and deployment protection rules.
Centralized secrets manager is worth adding when you have many secrets, multiple repositories sharing the same credentials, or when manual rotation is becoming a burden.
Security best practices regardless of approach
Use the principle of least privilege. Your test workflow doesn’t need production database credentials. Split secrets by the minimum access each job actually needs.
Rotate credentials regularly. API keys that never change are a liability. Set a reminder to rotate critical credentials every 90 days and update them in your secrets store.
Never print secrets to logs. GitHub masks known secrets in logs but custom formats might slip through. Audit your workflow logs regularly and avoid commands that might expose environment variables.
Review workflow permissions. By default GitHub Actions has broad repository permissions. Restrict them in your workflow file:
permissions:
contents: read
deployments: write
Be careful with pull request workflows. Secrets are not available to workflows triggered by pull requests from forks by default — this is intentional. Be careful about changing this behavior as it can expose secrets to untrusted code.
The minimal setup that works
If you’re starting from scratch, here’s the setup that covers most teams without overcomplicating things:
- Use GitHub environment secrets with
productionandstagingenvironments - Add protection rules requiring review before production deployments
- Restrict production deployments to your main branch
- Use a secrets manager like EnvMaster if you have more than 10-15 secrets or multiple repositories
That covers the basics securely without requiring significant infrastructure investment.
EnvMaster integrates with GitHub Actions using a single repository secret. Try it free — no credit card required.