GitHub Actions Tutorial — Deployment Protection
Deployment Protection
GitHub Environments let you attach protection rules to deployment targets. Jobs referencing an environment pause until all protection rules are satisfied — approvals, wait timers, or branch restrictions.
Environments
Create environments in Repository Settings → Environments:
Repository: my-org/my-app
├── staging
│ ├── No protection rules
│ └── Secrets: STAGING_URL
├── production
│ ├── Required reviewers: @lead-dev, @devops-team
│ ├── Wait timer: 5 minutes
│ ├── Branch policy: main only
│ └── Secrets: PROD_URL, DB_PASSWORD
Use in Workflows
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging # no gates — runs immediately
steps:
- run: echo "Deploying to ${{ vars.STAGING_URL }}"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # pauses for approval
steps:
- run: echo "Deploying to ${{ vars.PROD_URL }}"
Protection Rules
Required Reviewers
One or more people (or teams) must approve before the job runs:
Environment: production
Required reviewers: @alice, @bob, @devops-team
When a workflow reaches this job:
- Reviewers receive a notification
- Any one reviewer can approve or reject
- Job proceeds only after approval
Wait Timer
Add a delay before the job starts (even after approval):
Environment: production
Wait timer: 10 minutes
Useful for:
- Cool-down period between deploy stages
- Time to verify staging before production
- Allowing rollback window
Branch Restrictions
Limit which branches can deploy to an environment:
Environment: production
Deployment branches: main only
Options:
- All branches — any branch can deploy
- Protected branches — only branches with protection rules
- Selected branches — specific branch name patterns
Environment Secrets and Variables
Each environment has its own secrets and variables that override repository-level values:
jobs:
deploy:
environment: production
steps:
- run: |
# These come from the production environment
echo "URL: ${{ vars.APP_URL }}"
echo "Deploying..."
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
Multi-Environment Pipeline
name: Deploy Pipeline
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
deploy-staging:
needs: test
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- run: |
echo "Deploying to staging..."
# deploy commands here
smoke-test:
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- run: |
curl -f ${{ vars.STAGING_URL }}/health || exit 1
echo "Smoke test passed"
deploy-production:
needs: smoke-test
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- run: |
echo "Deploying to production..."
# deploy commands here
Flow: test → deploy staging → smoke test → (approval gate) → deploy production.
Common Mistakes
| Mistake | Fix |
|---|---|
| Environment secrets not found | Secrets are scoped to the environment — they’re only available in jobs with environment: |
| Job doesn’t pause for approval | Ensure the job has environment: <name> and reviewers are configured |
| Branch deploy rejected | Check branch restriction settings on the environment |
| Approval notification not received | Reviewers must have repo access and notification settings enabled |
Quick Reference
| Feature | Configuration |
|---|---|
| Create environment | Settings → Environments → New |
| Required reviewers | Add users or teams as reviewers |
| Wait timer | 0-43200 minutes (up to 30 days) |
| Branch policy | All, protected, or selected branches |
| Env secrets | secrets.NAME — only in jobs with environment: |
| Env variables | vars.NAME — only in jobs with environment: |
← Previous: Deploy Docker Images
Next: Practice Exercises →