Chapter 21Lesson 01~165 minutes

Protected Environments, Deployment Approvals, Freeze Windows, Manual Gates, and Separation of Duties: Concepts, Architecture, and Mental Model

Separate build success from deployment authorization: model protected environments, approvers, manual gates, freeze policy, deployment execution, and external health as independently observable states.

AuthorizationProtected environmentsApprovalsFreeze windowsSeparation of duties

Learning objectives

  • Explain why a successful build or even a successful deployment job is not itself authorization to change production.
  • Distinguish protected-environment authorization, deployment approval, a blocking manual job, a deploy freeze, protected variables, and external target health.
  • Describe the current tier boundary: deploy freezes and normal manual gates are Free; protected environments and deployment approvals are Premium/Ultimate.
  • Inspect deployment candidate identity, authorization state, deployment record, and external health without changing any state.
  • Design separation of duties so the artifact producer, deployment approver, deployer, and administrator are not accidentally treated as one trust role.

1. The practical problem: green is not the same as authorized

A pipeline can compile correctly, tests can pass, and an artifact can have a verified digest, yet a production change can still be unauthorized. Chapter 19 separated job success, GitLab deployment history, and external target health. This chapter adds another state boundary: who was allowed to authorize the change, under which policy, and with what audit evidence?

The safe model is deliberately conservative. Build and test jobs create evidence. Authorization policy decides whether a deployment candidate may cross a trust boundary. The deployment job performs the side effect. A health check then proves what happened outside GitLab. None of those states should be inferred from another.

Core rule: approval is permission to attempt a deployment, not proof that the deployment succeeded; job success is execution evidence, not proof the service is healthy.

2. Terms before configuration

Term Meaning here Do not confuse it with
Protected environment A GitLab environment with restricted deploy access; current docs place this in Premium/Ultimate. A protected branch or a protected CI/CD variable.
Allowed to deploy Users, groups, or roles permitted to deploy to a protected environment. Approvers; approval and execution can be separate roles.
Deployment approval A Premium/Ultimate authorization record required before a protected-environment deployment may proceed. MR approval; it controls deployment, not source merging.
Blocking manual job A job requiring a human action while preventing later stages from proceeding. Deployment approval; a manual job can exist without protected-environment approval rules.
Deploy freeze A scheduled policy interval exposed to pipelines through CI_DEPLOY_FREEZE. A universal external lock; other systems can still change the target unless governed too.
Separation of duties Different people/roles control source, approval, deployment, and override powers. Simply having two buttons or two jobs owned by the same authority.

3. State model: write down every boundary before changing it

Layer Evidence to capture Why it matters
Source/revision CI_PIPELINE_SOURCE, ref, CI_COMMIT_SHA, artifact digest Identifies the exact candidate.
Compiled pipeline Merged YAML, job inclusion, manual/freeze rules Shows what policy GitLab actually compiled.
Authorization Protected environment, allowed deployers, approver rules, approval history Shows who could authorize and who did.
Job/runner Pipeline/job IDs, actor, runner/executor, timestamps Shows who caused execution and where it ran.
Deployment record Deployment ID/status, environment, SHA, user Shows GitLab's deployment record.
External target Target ID, candidate digest/version, independent health result Shows what really changed.
Governance Freeze interval, exception ticket/reason, admin override evidence Explains exceptional policy decisions.

A useful audit packet ties these rows together. If any link is missing, the claim “authorized production deployment succeeded” is incomplete.

4. Mental model: candidate → authorization → execution → health → audit

First a verified build produces a deployment candidate. GitLab compiles the deployment job and determines whether the target environment is protected and whether a freeze/manual rule applies. An approval policy may block execution until eligible approvers act. A deployer then starts or unblocks the deployment job. The job changes the target. Only an independent health check can establish post-deploy health. Finally, the pipeline, approval, deployment, and health identifiers form the audit record.

flowchart TD
  A[Verified artifact + SHA] --> B[Compiled deployment candidate]
  B --> C{Freeze/manual policy}
  C --> D{Protected environment authorization}
  D --> E[Approval record]
  E --> F[Authorized deploy action]
  F --> G[GitLab deployment record]
  G --> H[External target change]
  H --> I[Independent health verification]
  I --> J[Audit packet + exception history]

The arrows are causal dependencies, not decorations. For example, the approval record must point to the same candidate/deployment that is later health-checked; otherwise a reviewer can accidentally approve one revision while another is deployed.

5. Protected environments: who may execute the production side effect

Current GitLab documentation lists protected environments as Premium/Ultimate. A Maintainer or Owner can protect an environment and configure deploy access. Users not in the Allowed to deploy set cannot update or deploy to that protected environment. GitLab administrators retain broad authority, which means administrator actions belong in the audit model rather than being treated as invisible exceptions.

Protected environments are not the same as protected branches. A branch rule controls source changes; an environment rule controls deployment access. Mature delivery uses both when both source integrity and deployment authority matter.

Mandatory lab boundary: this course does not require a paid tier. We simulate the same authorization contract locally with an allowlisted actor/approver file, while using real GitLab Free controls for manual jobs and deploy-freeze inputs.

6. Deployment approvals: authorization record, not rollout proof

Deployment approvals are also Premium/Ultimate. Required approvals block deployments to the protected environment until approval rules are satisfied. Current docs state that the pipeline triggerer cannot approve their own deployment by default; an explicit project option can allow self-approval. Administrators can approve or reject deployments, so admin intervention must be recorded as a governance event.

GitLab exposes approval details in the environment/deployment UI and through the Deployments API. The deployment can have a blocked status, and Premium/Ultimate responses can include approval summaries. After authorization, execution is still a separate event: approving does not prove the script ran or that the target became healthy.

7. Manual gates: useful, but model their blocking semantics precisely

Manual jobs exist on all tiers. Their pipeline behavior depends on allow_failure and where when: manual is declared. A top-level manual job defaults to optional behavior, while when: manual inside rules defaults to blocking behavior. To make intent unambiguous, use allow_failure: false for a blocking gate.

deploy_production:
  stage: deploy
  environment:
    name: production
  script:
    - ./ci/deploy-verified.sh "$CANDIDATE_DIGEST"
  when: manual
  allow_failure: false
  manual_confirmation: "Deploy the verified candidate to simulated production?"

manual_confirmation adds an explicit confirmation step, but it does not create independent authorization. If the same user can prepare, approve, and run the job, there is no true separation of duties.

8. Deploy freezes: policy input at pipeline creation

Deploy freeze periods are available on Free, Premium, and Ultimate. During an active freeze GitLab makes the pre-pipeline variable CI_DEPLOY_FREEZE=true available. That lets workflow, includes, and job rules decide whether a deployment job should be omitted, blocked, or converted to a controlled exception path.

deploy_production:
  stage: deploy
  script: ./ci/deploy-verified.sh "$CANDIDATE_DIGEST"
  environment:
    name: production
  rules:
    - if: '$CI_DEPLOY_FREEZE'
      when: manual
      allow_failure: false
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
      allow_failure: false
    - when: never

A freeze is not external target health and not an air gap. It is a delivery policy signal. Any emergency exception should record who authorized it, why, which exact SHA/digest was affected, and how the target was verified afterward.

9. Inspect first: prove policy state without mutating it

Before playing, approving, retrying, or changing settings, capture evidence. In an authorized GitLab project, start with the pipeline graph and merged configuration; then inspect Operate → Environments for deployment status and approval details. Read freeze periods before editing them. If API access is authorized, prefer GET requests and avoid logging tokens.

# Read-only examples. Supply an authorized token through the client/environment; do not print it.
curl --fail --silent --show-error   --header "PRIVATE-TOKEN: $GITLAB_READ_TOKEN"   "$CI_API_V4_URL/projects/$CI_PROJECT_ID/freeze_periods"

curl --fail --silent --show-error   --header "PRIVATE-TOKEN: $GITLAB_READ_TOKEN"   "$CI_API_V4_URL/projects/$CI_PROJECT_ID/deployments?environment=production"

The response should be stored only if its contents are appropriate for your evidence retention policy. Never dump all CI/CD variables to “debug” authorization.

10. Separation of duties: design roles around failure impact

Role Minimum responsibility Should not automatically imply
Builder Produce/test candidate and digest Production deployment authority
Approver Confirm candidate may proceed under policy Ability to rewrite artifact or deployment script
Deployer Execute an approved deployment Ability to approve own request by default
Verifier Measure independent target health Power to alter the result being measured
Administrator Recover/govern exceptional cases An undocumented bypass path

Small teams may combine roles operationally, but the course keeps the states separate so you can see exactly what assurance is lost when identities overlap.

11. Common mistakes and safer patterns

Mistake Why it fails Safer pattern
Treat green tests as authorization Tests answer correctness questions, not permission questions. Create an explicit deployment authorization layer.
Use one broad Maintainer role for everything Source, secret, deployment, and override powers collapse together. Use protected environments or a separate CD project where appropriate.
Assume approval means success Approval occurs before execution/health verification. Record deployment ID and independent post-deploy health.
Expose production secret to pre-gate jobs The secret crosses the trust boundary before authorization. Scope secrets to the deployment environment/job and minimize earlier job access.
Bypass a freeze with no record Emergency path becomes unauditable normal behavior. Require bounded reason, actor, candidate digest, and after-action evidence.

12. Summary and evidence checklist

  • A verified artifact is a deployment candidate, not deployment permission.
  • Protected environments and deployment approvals are Premium/Ultimate; manual jobs and freeze periods are available on Free.
  • Approval, manual execution, deployment status, and external health are separate states.
  • Default deployment self-approval is disabled; enabling it is an explicit reduction in separation of duties.
  • A freeze should be observable in pipeline policy through CI_DEPLOY_FREEZE, and exceptions need their own evidence.
  • Capture source SHA/digest, compiled configuration, policy/approval record, job/deployment IDs, actor, and post-deploy health.

Knowledge check

A production deployment job is green. Does that prove it was properly authorized?

What is the key difference between a blocking manual job and a deployment approval?

What does CI_DEPLOY_FREEZE tell you?

Why is “triggerer can self-approve” a governance decision rather than a convenience toggle?

What additional evidence is required after an approved deployment runs?

Next lesson

Guided hands-on workflow and core operations

Build the same control-plane model with a disposable candidate, a local authorization policy, a denied actor, freeze behavior, a blocking manual gate, deployment evidence, and independent health verification.

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.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.