Protected Environments, Deployment Approvals, Freeze Windows, Manual Gates, and Separation of Duties: Configuration, Design Choices, and Tradeoffs
Choose between manual jobs, protected-environment approvals, separation of duties, freeze policies, emergency exceptions, and narrow deploy roles using explicit trust, audit, tier, and operational tradeoffs.
Learning objectives
- Choose a manual gate or deployment approval based on the assurance required rather than UI preference.
- Evaluate self-approval, role separation, hard freezes, emergency exceptions, and narrow deployment authority.
- Understand when protected variables and environment scopes do or do not align with authorization timing.
- Map each design choice to tier prerequisites, affected GitLab/external state, and observable evidence.
- Document current assumptions so a future GitLab upgrade cannot silently change the authorization contract.
1. Design from the control objective, not from the button
A manual Play button, a protected environment, and an approval rule can all “look like gates,” but they answer different questions. The right design begins with the control objective: who may request, who may authorize, who may execute, what must be frozen, and which evidence must survive?
Keep CI_PIPELINE_SOURCE, CI_COMMIT_SHA,
compiled job rules, candidate digest, and external target identity
in the decision because authorization without candidate identity is
not reproducible.
2. Manual job versus environment approval
| Choice | Strength | Weakness | Tier |
|---|---|---|---|
| Blocking manual job | Simple, visible pipeline pause; Free. | Who can play it may be broad unless combined with paid protected-environment controls. | Free+ |
| Protected manual job | Limits who may play a deployment job to protected-environment deployers. | Requires protected environments. | Premium/Ultimate |
| Deployment approval | Creates explicit eligible approvers and approval history separate from deployment execution. | Paid feature; approval still does not prove rollout health. | Premium/Ultimate |
| Local/simulated approval contract | Good teaching/test path for policy logic. | Not a substitute for GitLab authorization in production. | Free/local |
3. Self-approval versus separation of duties
By default, current GitLab deployment approvals do not let the pipeline triggerer approve the same deployment. A project option can allow this. That option is useful in low-risk workflows, but it changes the assurance: the requester and approver become one identity.
| Scenario | Recommended posture | Evidence |
|---|---|---|
| Low-risk internal preview | Self-approval may be acceptable if documented. | Actor + candidate + deployment record. |
| Production with compliance requirement | Separate triggerer, approver, and deployer where practical. | Approval identity/history + deploy actor + health. |
| Emergency recovery | Permit bounded exceptional authority only with reason/incident reference and after-action review. | Exception record + exact digest/SHA + health + reviewer. |
4. Hard freeze versus emergency exception
A freeze is most useful when it is deterministic and visible before
job execution. CI_DEPLOY_FREEZE is pre-pipeline, so
rules can prevent normal production jobs from being included or can
transform them into an explicit emergency path. Avoid relying on a
shell check at the end of a privileged deployment job after secrets
and target credentials have already been exposed.
rules:
- if: '$CI_DEPLOY_FREEZE'
when: manual
allow_failure: false
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual
allow_failure: false
- when: never
For stricter organizations, the emergency job can be defined in a separately controlled include or deployment project so ordinary developers cannot rewrite the bypass condition.
5. Broad deploy role versus narrow authorization
Granting every Maintainer production deployment authority is easy but couples source administration, CI configuration, variable access, and production actions. A narrower protected-environment deployer set—or a separate deployment project introduced in Chapter 15—reduces blast radius.
Do not compensate for a broad role with secret masking. Masking protects logs, not authorization. Least privilege is about whether the identity can receive or use the credential at all.
6. Secrets and gate timing: do not expose production credentials before authorization
A common design error is to load a production credential in a preparation job, then place the manual or approval gate later. The credential has already crossed the trust boundary. Prefer environment-scoped/protected variables and structure the pipeline so the first job requiring the production credential is the actual authorized deployment job.
| State | Before authorization | After authorization |
|---|---|---|
| Candidate artifact | Readable as required by build/review roles | Same immutable candidate |
| Production credential | Not exposed to ordinary preparation/test jobs | Available only to authorized deployment context |
| External target write access | None | Narrow, time/job-scoped where possible |
7. Auditability: record both normal and exceptional paths
The environment UI and Deployments API provide deployment IDs, SHA/ref, actor, status, and—on Premium/Ultimate—approval summaries. Freeze-period APIs provide policy schedule evidence. Those are useful but incomplete without external health evidence and any emergency-exception reason.
Do not make “administrator did it” the end of an incident narrative. Administrator authority is a fact; the audit packet still needs candidate identity, action, reason, target effect, and verification.
8. Worked decision table
| Requirement | Recommended control | Prerequisite | Observe |
|---|---|---|---|
| Human acknowledgement only | Blocking manual job + confirmation | Free | Job creator/player, pipeline/job IDs |
| Only a small ops group may deploy | Protected environment Allowed to deploy | Premium/Ultimate | Environment protection config + deploy actor |
| Independent authorization | Deployment approval rules; self-approval disabled | Premium/Ultimate | Eligible approvers + approval history |
| Holiday/change freeze |
Freeze period + CI_DEPLOY_FREEZE-aware rules
|
Free | Freeze schedule + compiled job decision |
| Emergency during freeze | Dedicated exception path with narrow actor set and reason | Policy-specific | Exception ID, actor, exact candidate, post-health |
9. Current behavior assumptions to pin in operating documentation
- Protected environments: Premium/Ultimate; Maintainer or Owner can configure project protection; administrators retain access.
- Deployment approvals: Premium/Ultimate; default self-approval by pipeline triggerer is disabled; eligible approvers can approve/reject; approvals are separate from actual deployment execution.
-
Manual jobs: available on Free; explicit
allow_failure: falsemakes blocking intent clear. Insiderules, manual jobs default to blocking behavior. - Manual confirmation: supported for manual jobs; current docs also support environment stop-job confirmation.
-
Freeze periods: Free+;
CI_DEPLOY_FREEZEis pre-pipeline when active; maintainers/owners manage freeze periods through API/UI.
10. Failure isolation: authorization should not hide execution failures
If approval is correct but the deployment script fails, do not reopen or weaken the approval rule first. If the candidate digest is wrong, fix artifact flow. If the job cannot obtain a runner, fix scheduling. If the target is unhealthy after a successful job, investigate the provider/application. Layered evidence prevents policy controls from becoming a generic troubleshooting hammer.
11. Design review checklist
- State the control objective and tier prerequisite before choosing a gate.
- Tie approval to exact source SHA and candidate digest.
- Keep protected credentials out of pre-authorization jobs.
- Make freeze behavior visible in compiled configuration, not only in operator memory.
- Treat emergency bypass as a separately auditable workflow.
- Verify post-deployment target state independently of GitLab job/approval state.
Knowledge check
A team wants only a human acknowledgement before a low-risk internal deployment. Is Premium deployment approval required?
No. A blocking manual job may satisfy that control objective. Deployment approvals are for a stronger authorization model and are Premium/Ultimate.
Why can a shell-only freeze check be weaker than a rules-based gate?
Because the privileged job may already have started and received credentials before the shell check. A pre-pipeline rule can keep the normal deployment job out of the runnable path.
What assurance is lost when pipeline triggerers can self-approve?
Independent authorization. The requester and approver identities collapse, so the approval no longer proves separation of duties.
Where should production credentials first appear in a gated design?
In the narrow authorized deployment context, not in ordinary build/preparation jobs before the gate.
A deployment was approved but no runner is available. Which layer is failing?
Queue/runner scheduling, not authorization. Preserve the approval evidence and diagnose runner capacity/assignment separately.
Version and compatibility note
GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.
Official references and version notes
Documentation verification date: 2026-09-12. The mandatory learning path uses only disposable/local simulation plus GitLab Free features. Protected environments and deployment approvals are treated as Premium/Ultimate and are simulated unless the learner already has an authorized eligible project.
- Protected environments — official reference.
- Deployment approvals — official reference.
- Deployment safety — official reference.
- Job control — official reference.
- CI/CD YAML reference — official reference.
- Predefined variables — official reference.
- Freeze Periods API — official reference.
- Deployments API — official reference.
Current assumptions used in this chapter: Version-sensitive YAML, Runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations must be verified against the exact GitLab, GitLab Runner, tool, and external-system versions used in production. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for reproducible work.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.