An approval gate is a person deciding whether a change should reach production. Whether it adds safety or just latency depends almost entirely on whether the approver is given enough information to make a real decision — and most approval gates are not.
Configuring the gate
Section titled “Configuring the gate”-
Settings → Environments → the environment.
-
Enable Required reviewers and add up to six users or teams. Any one of them can approve.
-
Optionally set a wait timer — a delay before the job may start.
-
Optionally restrict deployment branches and tags to the refs allowed to deploy here.
jobs: deploy: runs-on: ubuntu-latest environment: name: production url: https://example.com steps: - run: ./deploy.shThe workflow file does not change. The gate is a property of the environment, which is what stops someone bypassing it by editing the workflow.
What happens when it runs
Section titled “What happens when it runs”The job enters a Waiting state before its first step. It has claimed no runner and executed nothing. Reviewers are notified, the pull request and run page show the pending deployment, and approving or rejecting requires a comment field that becomes part of the deployment record.
A rejected deployment fails the job. An unapproved one eventually expires — by default after 30 days, and the run then shows as failed rather than as still waiting.
Self-approval
Section titled “Self-approval”By default a user with access can approve their own deployment. There is a setting to prevent it, and whether it is available depends on the plan.
Whether to enable it is a genuine trade-off rather than an obvious win. Requiring a second person is the standard separation-of-duties control and it is what most compliance regimes expect. It also means that at 3am, the person fixing an outage cannot ship the fix alone. Teams that enable it generally pair it with a documented break-glass path — a separate environment with different reviewers, or a process for temporarily relaxing the rule that is itself logged.
Decide deliberately, and write down what happens during an incident.
Give the approver something to approve
Section titled “Give the approver something to approve”This is the part that determines whether the gate does anything. “Deploy to production?” with no context gets approved reflexively within a week of being introduced.
Write the decision-relevant facts into the job summary of the job before the gate:
prepare: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: fetch-depth: 0
- name: Summarise what will deploy run: | { echo "## Deployment candidate" echo "" echo "**Commit:** \`${GITHUB_SHA}\`" echo "" echo "### Commits since the last production deployment" echo "" git log --oneline "$(git describe --tags --abbrev=0 --match 'prod-*' 2>/dev/null || echo HEAD~10)..HEAD" echo "" echo "### Migrations in this change" echo "" git diff --name-only "$(git describe --tags --abbrev=0 --match 'prod-*' 2>/dev/null || echo HEAD~10)..HEAD" -- migrations/ || echo "none" } >> "$GITHUB_STEP_SUMMARY"
deploy: needs: prepare environment: production runs-on: ubuntu-latest steps: - run: ./deploy.shThe two things an approver most needs are what changed and whether anything is irreversible. For an infrastructure pipeline that means the Terraform destroy count — see deploying Terraform. For an application it means whether there is a database migration, because that is the part a rollback will not undo.
fetch-depth: 0 is required for git log to see history; the default checkout is shallow.
What approval does not verify
Section titled “What approval does not verify”Being honest about this matters, because approval gates are often treated as a stronger control than they are:
- It does not verify the code. Approval happens after merge; code review happened earlier, or did not.
- It does not verify the artifact matches the commit unless you have provenance attestation.
- It does not prevent a compromised workflow from doing something else. The approver approves a deployment, not a specific set of commands. A malicious change to the deploy script is approved along with everything else.
- It does not make the deployment reversible.
The control approval genuinely provides is a timing gate with a named human and an audit record. That is valuable — it is what stops an automated pipeline from shipping at 5pm on a Friday, and it gives an auditor a name — but it is not code review and should not be relied on as such.
Wait timers
Section titled “Wait timers”Wait timer: 10 minutesThe job holds for the configured period before it may start, or before reviewers can approve. It is useful in two shapes:
A cancellation window after an automatic staging deployment, so metrics and alerts have time to say something before production follows.
A cooling-off period on a sensitive environment, which prevents a mistake being compounded immediately by a hurried second attempt.
Approving from the CLI
Section titled “Approving from the CLI”gh run view <run-id>gh api -X POST repos/OWNER/REPO/actions/runs/<run-id>/deployment_protection_rule \ -f environment_name=production -F state=approved -f comment="Reviewed migrations, none present"Useful for on-call. Note that scripting approvals is in tension with the point of having them — an approval issued by automation is not a human decision, however it is recorded.
Exercise
Section titled “Exercise”-
Add required reviewers to a
productionenvironment and deploy. Watch the job sit in Waiting with no runner claimed. -
Reject it with a comment. Confirm the job fails and the comment is recorded.
-
Add the
preparejob above so the approver sees the commit list and any migrations before deciding. -
Add a 5-minute wait timer and confirm approval is not offered until it elapses.
-
Restrict the environment to
main. Try deploying from a branch and confirm it is refused before the gate. -
If your plan offers it, enable “prevent self-review” — then write down what your team does at 3am when only one person is awake.
Then what?
Section titled “Then what?”You have finished the advanced cluster. Security is next, and several pages here pointed into it.