Environments, Required Reviewers, Protection Rules, and Deployment Gates: Core Concepts and Mental Model
Chapter 18 standardized how workflows are adopted and reused. Chapter 19 adds a different control plane: an environment such as staging or production represents a governed deployment target, not a branch alias. The chapter separates source revision, deployment authorization, credential availability, GitHub deployment records, and the target state that a deployment actually changes.
Learning objectives
- Explain the control chain from successful build to protected deployment and deployment status evidence.
- Distinguish branch protection, environment protection, CI checks, and external target health.
- Predict exactly when environment secrets and variables become available to a job.
- Inspect environment rules, deployment records, concurrency groups, and target evidence before changing them.
- Design a deployment gate that does not mistake approval for artifact integrity or rollout success.
1. The practical problem: a successful build is not deployment authorization
A workflow can produce a correct build for an exact commit and still be unauthorized to change a deployment target. Production delivery adds independent questions: which target is being requested, which refs may deploy, who or what must approve, when credentials may become available, whether another deployment is already changing the same target, and what evidence proves the external target reached the intended state.
An environment gives GitHub a named target boundary such as
lab-staging, production, or
eu-west-1. A branch such as main can be
one input to authorization, but it is not the target itself.
Treating main == production collapses source selection,
authorization, credentials, concurrency, and deployment history into
one name and hides the controls Chapter 19 needs to make explicit.
2. Mental model: build evidence enters a governed deployment boundary
The causal chain begins with a build that already identifies its event, ref, source SHA, workflow revision, runner context, and build evidence. A deployment job then requests one environment. GitHub evaluates that environment's protection rules before sending the job to a runner. Only after the applicable rules pass can the job run and use environment-scoped secrets and variables. By default, referencing the environment also creates a GitHub deployment object and status history. The job may then change an external system—but that external state is separate from GitHub's record and must be verified independently.
flowchart TD A[Successful build for exact SHA] --> B[Deployment job requests environment] B --> C[Branch/tag policy] C --> D[Wait timer / reviewers / custom rules] D --> E[Job becomes eligible for runner] E --> F[Environment secrets + vars available] F --> G[Serialized deployment operation] G --> H[GitHub deployment + status record] G --> I[External target state] H --> J[Evidence packet] I --> J
The two final arrows matter: GitHub may record a successful deployment job while the application is unhealthy later, and an external system may change even if a later workflow step is cancelled. Deployment records and target health are related evidence, not interchangeable evidence.
3. State inventory before changing an environment
| State | Examples to capture | Why it matters |
|---|---|---|
| Event/source | event name, ref, exact SHA, workflow revision | Proves what source requested deployment |
| Environment | name, URL, deployment true/false | Identifies GitHub target boundary and history mode |
| Protection | reviewers, self-review, timer, custom rules, branch/tag policy | Explains why the job is waiting/allowed/denied |
| Credential/config | environment secret names, vars names, availability timing | Shows scope without exposing values |
| Job/runner | job ID, conclusion, runner OS/image | Separates gate decisions from execution runtime |
| Concurrency | group value, cancel-in-progress result, queued/in-progress run IDs | Explains serialization/cancellation |
| GitHub deployment | deployment ID, status IDs/states, environment URL | GitHub control-plane audit trail |
| External target | release/artifact digest, provider resource state, health result | Proves the actual deployment outcome |
Keep these state domains separate during incident response. A failed branch-policy check is not a runner capacity problem. A reviewer rejection is not an artifact-integrity failure. A successful deployment status is not proof that an external service is healthy. The evidence-first workflow starts by locating the layer that actually denied or changed state.
4. Protection rules: who or what must allow the job to proceed?
Current GitHub environments can use required reviewers, a wait timer, branch/tag restrictions, and custom deployment protection rules. Required reviewers can name up to six users or teams; only one of the configured reviewers needs to approve a pending job. You can optionally prevent a deployment initiator from approving their own run. A wait timer accepts 1 to 43,200 minutes and its waiting time is not billed as runner time because the protected job has not started on a runner yet.
Custom deployment protection rules are implemented by GitHub Apps and can integrate observability, change management, quality, or other external decision systems. GitHub currently allows a maximum of six deployment protection rules enabled on one environment. Custom rules are an authorization gate, not a replacement for verifying the built artifact or the target after rollout.
5. Branch and tag restrictions are environment policy, not branch protection
Deployment branches and tags decide which
GITHUB_REF values may deploy to an environment. The
environment can allow all refs, only protected branches, or selected
branch/tag patterns. Patterns for branches and tags are configured
separately. This is different from branch protection or rulesets,
which govern repository changes such as pushes, merges, reviews, and
status checks.
A robust production design can use both: branch protection
establishes how a source revision reaches main;
environment rules establish whether that ref may target
production. Neither control proves that the produced
artifact has the expected digest or that a rollout succeeded.
6. Environment credentials are withheld until the gate passes
Environment secrets are available only to jobs that reference that environment. Organization and repository secrets are read when a workflow run is queued; environment secrets are read when the job referencing the environment starts. If approval or another environment rule blocks the job, it cannot access the environment secret while waiting.
jobs:
deploy:
runs-on: ubuntu-24.04
environment: production
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
steps:
- shell: bash
run: |
if [ -z "$DEPLOY_TOKEN" ]; then
echo "credential_state=missing" >&2
exit 1
fi
echo "credential_state=present"
The example prints only a Boolean-like presence state. It never
prints the secret value. Environment variables are non-secret
configuration and are accessed through the
vars context, for example
${{ vars.DEPLOY_SLOT }}.
7. Referencing an environment normally creates deployment records
By default, when a workflow job references an environment, GitHub
creates a deployment object for that environment and deployment
status objects as the job progresses. If an
environment.url is supplied, it is recorded as the
deployment environment URL and can appear in the run visualization
and deployment UI. Those records are GitHub-owned history tied to
the deployment request—not the external service itself.
environment:
name: lab-staging
url: https://example.invalid/ch19/${{ github.run_id }}
Current workflow syntax also supports
deployment: false. This keeps environment
secrets/variables and still enforces wait timers and required
reviewers, but suppresses the GitHub deployment object. Custom
deployment protection rules require a deployment object and are
therefore incompatible with deployment: false. Use this
mode for CI/configuration cases intentionally—not as a way to hide
real production deployments from history.
8. Authorization and serialization solve different problems
An environment gate answers “may this job deploy?” Concurrency answers “may another job change the same target at the same time?” A production-like target should usually have a target-specific concurrency group whose value cannot accidentally collide with unrelated environments.
concurrency:
group: deploy-${{ github.repository }}-lab-staging
cancel-in-progress: false
cancel-in-progress: false is deliberate for a
state-changing operation: a newer deployment waits instead of
cancelling a deployment that may already have committed side
effects. Chapter 11's cancellation rule still applies—cancellation
cannot undo an external write that already happened.
9. Current plan and visibility assumptions
| Capability | Public repo | Private/internal note |
|---|---|---|
| Environments | Available on current plans | Requires Pro/Team/Enterprise as documented |
| Environment secrets | Available on current plans | Free requires public; private/internal requires eligible paid plan |
| Deployment branch/tag rules | Available for public repositories | Private/internal depends on eligible paid plan |
| Required reviewers / wait timer | Free/Pro/Team public repositories supported | On Free/Pro/Team these rules are public-only; Enterprise/private availability differs |
| Custom deployment protection rules | Public repositories on current plans subject to limits | Private/internal requires eligible plan/visibility |
The mandatory labs in this chapter therefore use a learner-owned public disposable repository, which keeps the path free-compatible and allows a real wait-timer gate. A required-reviewer extension needs an eligible second user/team if you want to prove human approval rather than simulate it.
10. Read-only inspection first
# Repository and workflow revision
git rev-parse HEAD
git status --short
# Inspect the workflow before dispatch
grep -n -E 'environment:|concurrency:|permissions:' .github/workflows/ch19-environment-lab.yml
# Optional authenticated local inspection with GitHub CLI
gh api \
-H "X-GitHub-Api-Version: 2026-03-10" \
repos/OWNER/REPO/environments/lab-staging
gh api \
-H "X-GitHub-Api-Version: 2026-03-10" \
'repos/OWNER/REPO/deployments?environment=lab-staging&per_page=5'
The API examples are read-only. Use a learner-owned repository and your existing authenticated CLI session; do not paste credentials into the command line or lesson logs.
11. Lesson summary
- An environment is a governed deployment target, not a branch alias.
- Protection rules gate a job before it reaches a runner.
- Environment secrets become available only to the environment job after the applicable gate passes.
-
Environment reference normally creates deployment/status history;
deployment: falseis an explicit exception. - Concurrency serializes target mutation; approval does not serialize it automatically.
- Deployment history and external target health must both appear in evidence.
Knowledge check
A workflow is approved for production. Does
approval prove the artifact digest is correct?
No. Approval authorizes the environment job. Artifact identity/integrity must be verified separately, ideally before the privileged deployment step.
When does an environment secret become available?
Only to a job that references that environment, after applicable environment protection rules pass and the job starts.
Why is main not the same concept as
production?
main identifies repository source state.
production identifies a governed deployment target
with its own rules, credentials, concurrency, deployment
records, and external state.
What does deployment: false change?
It suppresses GitHub deployment-object creation while retaining environment secrets/variables and wait/reviewer gates; custom deployment protection rules cannot be used with it.
Why use target-specific concurrency in addition to environment approval?
Approval decides whether a job may deploy; concurrency prevents overlapping jobs from mutating the same target at once.
Official references and version notes
Version-sensitive GitHub Actions behavior in this lesson was rechecked on 2026-09-10. Re-verify current plan, repository visibility, API, environment, and protection-rule behavior before production rollout.
- GitHub Docs — Deployments and environments
- GitHub Docs — Managing environments for deployment
- GitHub Docs — Reviewing deployments
- GitHub Docs — Deploying with GitHub Actions
- GitHub Docs — Deploying to a specific environment
-
GitHub Docs — Workflow syntax: jobs.
.environment - GitHub Docs — REST API for deployment environments
- GitHub Docs — REST API for deployments
- GitHub Docs — REST API for deployment statuses
- GitHub Docs — Secrets reference
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.