Chapter 18Lesson 01~270 minutes

Manual Jobs, Delayed Jobs, Scheduled Pipelines, Rollbacks, Freeze Windows, and Release Controls: Concepts, Architecture, and Mental Model

Add people, time, ownership, freshness, and rollback controls to the deployment model without mistaking a manual button or calendar window for complete authorization.

Manual jobsDelayed jobsSchedulesFreeze windowsRollbackRelease gates

Learning objectives

  • Separate manual, delayed, scheduled, freeze, rollback, and approval controls by the state they govern.
  • Explain optional versus blocking manual jobs and the permissions attached to the target ref/environment.
  • Distinguish an in-pipeline delay from server-side recurring pipeline creation.
  • Explain why a deploy freeze is a time signal/policy input rather than a complete authorization system.
  • Model stale-release prevention and known-artifact rollback without treating a retry button as evidence.
Availability baseline (verified 2026-08-22 against GitLab 19.3 current documentation). Manual jobs, manual_confirmation, delayed jobs with start_in, pipeline schedules, deployment freeze periods / CI_DEPLOY_FREEZE, environment/deployment history, the project setting that prevents outdated deployment jobs, and rollback/retry controls are available on GitLab Free/Premium/Ultimate across GitLab.com, Self-Managed, and Dedicated. Protected environments and deployment approvals—the fine-grained “only these people may deploy” layer—remain Premium/Ultimate. The mandatory chapter path uses only a disposable project, synthetic artifacts/environments, and tiny jobs. No production endpoint, cloud account, paid tier, runner registration, real credential, or release publication is required.

1. The practical problem: release automation needs both motion and restraint

Chapters 10–17 built the mechanics of GitLab CI/CD: jobs run on runners, rules decide existence, variables cross trust boundaries, artifacts carry identity, and deployments create environment history. A production release adds a second question: even if a deployment job can run, should it run now, under this actor, for this commit, against this environment?

Teams usually need several kinds of restraint. A person may need to make a conscious decision. A rollout may need to wait. A recurring maintenance pipeline may need to exist even when nobody pushes code. A holiday freeze may discourage routine releases. And if an older deployment is still waiting after a newer one succeeds, the old path must not silently overwrite the new state.

The mistake is to call all of these things “approval.” They are different controls with different failure modes.

2. Mental model: five clocks and identities around one deployment

Release-control flow
flowchart TD
  C[Commit + artifact identity] -- pipeline --> P[Pipeline]
  P -- optional human decision --> M[Manual job]
  P -- elapsed time --> D[Delayed job]
  S[Pipeline schedule owner + cron] -- creates --> P2[Scheduled pipeline]
  F[Freeze period] -- sets CI_DEPLOY_FREEZE --> G[Deployment rules / policy]
  M --> X[Deployment job]
  D --> X
  G --> X
  X -- environment record --> E[(Environment / deployment history)]
  N[Newer deployment] -. stale-path check .-> X
  R[Known artifact / previous commit] -- new recovery deployment --> E

The commit/artifact identity answers what. The pipeline source and schedule owner answer why/under whose permissions the pipeline exists. Manual/delayed state answers when the job may start. Freeze logic adds a time-policy signal. Stale-deployment controls answer whether an older job is still safe. Rollback creates another deployment action; it does not erase the failed/newer history.

3. Manual jobs: a human start condition, not automatically a strong approval policy

A job with when: manual exists in the pipeline but waits for a person to start it. GitLab currently requires the actor to have permission to merge to the assigned branch. For a sensitive protected environment, Premium/Ultimate protected-environment rules can narrow the allowed deployers further.

There are two commonly confused manual-job defaults:

Definition Default allow_failure Pipeline behavior Typical use
when: manual outside rules true Optional manual job; pipeline can complete without it. Optional diagnostics, optional promotion, cleanup. ...
rules: ... when: manual false Blocking manual job by default; pipeline is blocked until someone runs it. A deliberate release gate when policy allows it.
optional_smoke:
  stage: verify
  script: echo "synthetic smoke"
  when: manual

blocking_release_gate:
  stage: deploy
  script: echo "synthetic release gate"
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
      allow_failure: false

Make the behavior explicit instead of relying on defaults. A reviewer should be able to tell whether skipping the job blocks the pipeline by reading the YAML.

4. Confirmation reduces accidental clicks, not malicious authority

manual_confirmation adds an explicit confirmation step. It is useful for destructive or sensitive manual jobs because the operator must acknowledge the action. It does not change who is authorized to run the job.

deploy_synthetic:
  stage: deploy
  script: echo "would deploy known artifact"
  when: manual
  manual_confirmation: "Deploy the reviewed synthetic artifact?"

For fine-grained “only this operations group may deploy production,” use protected environments where available, plus target-system IAM. A confirmation dialog is not role separation.

5. Manual parameters: job inputs are safer than ad-hoc secret variables

Current GitLab lets manual jobs be run with job-specific variables and, in recent versions, typed job inputs. Manually supplied variables are visible to other project members who can rerun the job under documented project-visibility rules, can override existing variables, and are not a good place to paste credentials. Use validated inputs for non-secret configuration and external/managed secrets for credentials.

Never paste a real deploy password into the manual-job variables form for this course. The chapter uses only enum-like synthetic inputs such as rollout mode or target label.

6. Delayed jobs: elapsed time inside an already-created pipeline

when: delayed plus start_in creates a job now but schedules its start later. The supported delay is from one second through one week. The timer begins when the job’s stage becomes eligible; a failed prior stage prevents the timer from progressing into execution.

timed_canary:
  stage: deploy
  script: echo "synthetic delayed rollout"
  when: delayed
  start_in: 30 seconds

A delayed job is still tied to the original pipeline and commit. That is precisely why stale-code risk exists: while it waits, a newer pipeline may deploy a later commit. A time delay is not a freshness guarantee.

Operators can Unschedule a delayed job to stop its timer, then run it manually if appropriate. That action should be recorded as an exception, not treated as invisible routine.

7. A subtle syntax rule: start_in moves inside rules

If a job uses rules, put both when: delayed and start_in in the matching rule. Defining start_in at job level while using rule-driven when causes CI configuration validation to fail.

deploy_later:
  stage: deploy
  script: echo "delayed synthetic deployment"
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: delayed
      start_in: 2 minutes
    - when: never

8. Pipeline schedules create new pipelines; they do not delay an existing one

A pipeline schedule is a server-side object with a cron pattern, target ref, owner, time zone, active state, and optional inputs/variables. At each scheduled time GitLab creates a new pipeline whose source is schedule. This is appropriate for periodic maintenance, nightly validation, or routine release checks independent of code pushes.

The schedule runs with the schedule owner’s permissions. If that owner is blocked or removed, the schedule can become inactive. Maintainers/Owners can take ownership. A manual “Run” of a schedule uses the triggering user’s permissions instead of the schedule owner’s permissions.

Control Identity anchored to Clock anchored to If code changes while waiting
Delayed job Original pipeline + original SHA Elapsed delay after stage becomes eligible Still points to old pipeline/SHA unless stale-deploy controls stop it.
Pipeline schedule Schedule owner + target ref resolved for each run Cron + time zone Each occurrence creates a fresh pipeline at the then-resolved ref.

9. A schedule is a delegated identity object

Because a schedule uses its owner’s permissions, review ownership just as you review service identities. A schedule targeting a protected branch requires the owner to retain the relevant merge permission; protected tags require permission to create that tag. Prefer pipeline schedule inputs for configuration because they are typed/validated and recommended by current GitLab documentation; do not store long-lived credentials in schedule variables.

For the mandatory lab, you create an inactive disposable schedule or use a fixture. That proves the schedule object and owner/ref/cron state without starting recurring compute.

10. Deploy freeze: GitLab emits a time-window signal; your pipeline enforces behavior

Deployment freeze periods are Free across all offerings. A Maintainer/Owner can define cron-style start/end windows and a time zone. When a pipeline is created during a freeze window, GitLab exposes the pre-pipeline variable CI_DEPLOY_FREEZE=true.

This distinction matters: the freeze window does not replace authorization and should not be described as a magic firewall. Your rules/policy must decide what the deployment job does when the variable is present.

deploy_release:
  stage: deploy
  script:
    - echo "synthetic deployment only"
  rules:
    - if: '$CI_DEPLOY_FREEZE'
      when: manual
      allow_failure: true
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
      allow_failure: false
    - when: never

During a freeze, this example converts routine deployment into an explicit optional exception path. A production organization should also define who may invoke exceptions, where the reason is recorded, and how target-system authorization works.

11. Prevent outdated deployment jobs: freshness is a first-class release invariant

GitLab’s project setting Prevent outdated deployment jobs stops an older deployment job from overwriting a deployment that GitLab considers newer. An outdated job fails with an explicit reason, and an outdated manual deployment can have its Run button disabled.

Job “age” for this safety mechanism is based on job start ordering, not simply commit date. That nuance is why you should inspect deployment/job IDs and timestamps rather than assuming “newer SHA always wins.” Combine the setting with resource_group when one environment must be mutated by only one job at a time.

12. Rollback: create a new controlled deployment of known state

Rollback is a release action, not time travel. GitLab can retry/redeploy an older deployment under configured rollback behavior, but sensitive projects should be cautious with retries. Current GitLab deployment-safety guidance recommends disabling pipeline retries in sensitive projects and, when rollback is required, running a new pipeline with the previous commit.

The stronger invariant from Chapters 16–17 remains: deploy the same known artifact identity if possible. Rebuilding an old commit against mutable package repositories can produce different bytes and is therefore not evidence of rollback.

13. A manual button is not a change-management system

A production release gate generally needs more context than “someone clicked Play”:

Evidence Question answered
MR/review/commit SHA What code was approved?
Artifact digest What exact bytes are being promoted?
Manual/schedule actor Who caused this release path to proceed?
Environment/deployment ID Where did GitLab record the deployment?
Freeze/exception record Was normal release timing suspended or bypassed?
Runner/job token/target IAM What execution identity had authority?
Post-deploy verification Did the external target actually reach the intended state?

Chapter 24 will cover GitLab Release objects/tags/evidence in depth. Here, “release controls” means gating the operational deployment path, not publishing a Release object.

14. Read-only inspection before changing any release control

PROJECT_ID="12345678"

# Inspect project CI/deployment safety settings (selected fields only).
glab api "projects/$PROJECT_ID" --jq '{id,path_with_namespace,default_branch,ci_forward_deployment_enabled,ci_forward_deployment_rollback_allowed}'

# Existing pipeline schedules: owner/ref/cron only.
glab api "projects/$PROJECT_ID/pipeline_schedules" --paginate \
  --jq '.[] | {id,description,ref,cron,cron_timezone,active,owner:.owner.username,next_run_at}'

# Existing freeze periods: time-policy metadata only.
glab api "projects/$PROJECT_ID/freeze_periods" --paginate \
  --jq '.[] | {id,freeze_start,freeze_end,cron_timezone}'

# Recent deployments: preserve SHA/job ordering before touching stale paths.
glab api "projects/$PROJECT_ID/deployments?order_by=finished_at&sort=desc" \
  --jq '.[] | {id,status,sha,ref,environment:.environment.name,job:(.deployable.id // null),finished_at}'

Do not dump all pipeline variables, schedule variables, job environments, or tokens. Inspection should answer the release-control question with the minimum metadata necessary.

15. DevOps connection: release safety is state-machine design

A reliable release path makes invalid transitions difficult: unreviewed code should not become a deployable artifact; a stale delayed job should not overwrite a newer deployment; a freeze should turn routine automation into a controlled exception path; a schedule should have a current owner; and rollback should preserve artifact identity. These are state-machine invariants, not UI preferences.

Knowledge check

What is the default difference between when: manual outside rules and inside rules?

Does manual_confirmation narrow who is authorized to deploy?

How is a delayed job different from a pipeline schedule?

What does a deploy freeze give the pipeline?

Why can a delayed deployment become unsafe even if its timer is correct?

Why is rebuilding an old commit a weaker rollback than redeploying a known artifact?

Summary

Manual jobs model human start conditions; delayed jobs model elapsed time inside one pipeline; pipeline schedules create recurring new pipelines under an owner identity; deploy-freeze periods provide a policy signal; stale-deployment prevention protects environment freshness; and rollback must preserve known state. These controls compose—they do not substitute for ref protection, environment authorization, runner isolation, artifact identity, or target IAM.

Official references

Next lesson

Build the controls in a disposable pipeline

Lesson 2 creates a tiny synthetic release workflow, proves optional and blocking manual behavior, exercises a short delayed job, creates an inactive schedule, models freeze behavior, and performs a known-artifact recovery without touching production.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.