Checkpoint Lab — Manual Jobs, Delayed Jobs, Scheduled Pipelines, Rollbacks, Freeze Windows, and Release Controls
Prove the chapter end to end with a synthetic controlled release, superseded stale path, schedule/freeze evidence, recovery, and targeted cleanup.
Checkpoint objectives
- Predict actor/time/source/SHA/artifact state before every release-control transition.
- Operate one manual gate and one timed control against disposable output.
- Capture one safe stale/missing release path and diagnose the responsible control.
- Model a freeze exception and rollback without production infrastructure or paid governance.
- Remove schedules/freeze objects/refs by exact identity and preserve the audit story.
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. Checkpoint scenario
You are release engineer for a synthetic application. Every
“deployment” is only a checksum verification plus a GitLab
environment record at release-control/ch18-checkpoint.
The workflow must prove that humans, time gates, schedules, freeze
policy, stale-path prevention, and rollback all preserve one
invariant:
the environment may change only through an identified release
path tied to a known commit/artifact.
2. Preflight
| Item | Required |
|---|---|
| Offering/tier | GitLab.com, Self-Managed, or Dedicated; Free is sufficient for mandatory path. |
| Role | Developer for branch/pipeline; Maintainer/Owner only if creating schedule/freeze or changing stale-deployment setting. |
| Runner | Tiny eligible runner or no-runner fixture/static-validation path. |
| External target | None; reserved example.invalid URL only. |
| Secrets | None. No manual job variables containing credentials. |
| Paid controls | Protected environment / deployment approvals are architecture fixtures only. |
3. Capture before-state
PROJECT_ID="12345678"
glab api "projects/$PROJECT_ID" \
--jq '{default_branch,ci_forward_deployment_enabled,ci_forward_deployment_rollback_allowed}'
glab api "projects/$PROJECT_ID/pipeline_schedules" --paginate \
--jq '.[] | {id,description,ref,active,owner:.owner.username}'
glab api "projects/$PROJECT_ID/freeze_periods" --paginate \
--jq '.[] | {id,freeze_start,freeze_end,cron_timezone}'
glab api "projects/$PROJECT_ID/environments?name=release-control/ch18-checkpoint" \
--jq '.[] | {id,name,state,last_deployment:(.last_deployment.id // null)}'
Choose unique resource names/IDs. Record whether Prevent outdated deployment jobs is already enabled; do not change a shared project setting without ownership/approval.
4. Write predictions before execution
| Event | Prediction |
|---|---|
| Build | Creates one checksum-addressed synthetic artifact tied to checkpoint SHA. |
| Manual gate not run | Pipeline remains blocked at release gate. |
| Manual gate run by eligible actor | Gate succeeds; delayed deployment becomes scheduled. |
| Delayed job timer active | No runner slot is used until it becomes runnable; later stage does not progress through this gate. |
| Newer pipeline deploys first | Older delayed deployment must be canceled/unscheduled or rejected as stale. |
| Freeze fixture true | Routine deploy changes to documented manual exception behavior. |
| Rollback/recovery | Creates a new deployment action using known prior artifact identity; history remains intact. |
5. Checkpoint pipeline
stages: [build, gate, deploy, verify]
build_release:
stage: build
script:
- mkdir -p out
- printf 'sha=%s\npipeline=%s\n' "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" > out/release.txt
- sha256sum out/release.txt > out/release.txt.sha256
artifacts:
name: "ch18-$CI_COMMIT_SHORT_SHA"
paths: [out/release.txt, out/release.txt.sha256]
expire_in: 2 days
access: developer
release_gate:
stage: gate
needs:
- job: build_release
artifacts: true
script:
- sha256sum -c out/release.txt.sha256
- echo "gate_actor_confirmed=true"
when: manual
allow_failure: false
manual_confirmation: "Proceed with synthetic release?"
release_delayed:
stage: deploy
needs:
- job: release_gate
artifacts: true
resource_group: ch18-checkpoint
script:
- sha256sum -c out/release.txt.sha256
- echo "synthetic_deploy=true"
when: delayed
start_in: 45 seconds
environment:
name: release-control/ch18-checkpoint
url: https://ch18-checkpoint.example.invalid
verify_release:
stage: verify
needs: [release_delayed]
script:
- echo "verify_pipeline=$CI_PIPELINE_ID"
- echo "verify_sha=$CI_COMMIT_SHA"
6. Validate and reason about the merged YAML
Use CI Lint/Pipeline Editor. Confirm no secret, external endpoint, registry push, package publish, token creation, or production tag exists. Confirm the gate is blocking, the delayed job is bounded, all deployment paths share the same synthetic environment/resource-group identity, and the artifact checksum is checked before every simulated deploy.
7. Create the checkpoint ref
git switch -c ch18/checkpoint
git add .gitlab-ci.yml
git diff --cached --check
git diff --cached
git commit -m "ch18: controlled release checkpoint"
CHECKPOINT_SHA="$(git rev-parse HEAD)"
printf 'checkpoint_sha=%s\n' "$CHECKPOINT_SHA"
git push -u origin ch18/checkpoint
8. Prove the manual gate is blocking
Before running it, record the pipeline status and
release_gate status. Verify
release_delayed has not executed. Then run the gate
without any ad-hoc variables. Record the actor, job ID, and SHA.
Prediction check: the gate succeeds only after a human starts it; the pipeline does not silently continue while it is waiting.
9. Exercise the timed path
After the gate succeeds, inspect release_delayed.
Record its scheduled/delayed state and timestamps. For the first
pipeline, do not let it deploy yet; this pipeline
becomes the intentionally stale path.
10. Create a newer pipeline and supersede the stale path
Make a harmless change to the synthetic payload (for example add
generation=2), commit, push, run the manual gate in
Pipeline B, and allow its delayed deployment to complete first.
Record Pipeline B’s SHA/artifact checksum/deployment ID.
Now return to Pipeline A. Choose one safe path:
- Preferred lab action: Unschedule/cancel Pipeline A’s delayed deployment after preserving its IDs; document “superseded by Pipeline B.”
- If testing stale-deployment setting in a dedicated project: leave it runnable and prove GitLab rejects/disables it as outdated after the newer deployment. Do not weaken the setting to force it through.
This is the required intentionally missing/rejected release path.
11. Capture stale-path evidence
# Example selected metadata; use actual IDs.
glab api "projects/$PROJECT_ID/pipelines/$PIPELINE_A/jobs" --paginate \
--jq '.[] | select(.name=="release_delayed") | {id,name,status,sha,created_at,started_at,finished_at}'
glab api "projects/$PROJECT_ID/pipelines/$PIPELINE_B/jobs" --paginate \
--jq '.[] | select(.name=="release_delayed") | {id,name,status,sha,created_at,started_at,finished_at}'
glab api "projects/$PROJECT_ID/deployments?environment=release-control/ch18-checkpoint&order_by=finished_at&sort=desc" \
--jq '.[] | {id,status,sha,job:(.deployable.id // null),finished_at}'
Explain which control stopped the old path: operator unschedule/cancel, GitLab outdated-deployment policy, or both. “It didn’t run” is not enough.
12. Add one inactive schedule and record ownership
Create ch18 checkpoint schedule targeting
ch18/checkpoint, cron 23 4 * * *, UTC,
inactive. Capture schedule ID, owner, ref, cron, and active state.
Do not add secret variables. If you choose a manual one-time Run for
additional evidence, record that source=schedule and
that the one-time run used the triggering user’s permissions.
13. Freeze-policy exercise
Do not manipulate the system clock. Use this policy fixture:
{
"freeze_period": {"start": "0 22 * * 5", "end": "0 8 * * 1", "timezone": "UTC"},
"pipeline_pre_variable": {"CI_DEPLOY_FREEZE": "true"},
"expected_release_behavior": "manual exception path only",
"required_exception_evidence": ["actor", "reason", "artifact_digest", "post_deploy_verification"]
}
Optional live exercise in a dedicated project: create a disposable
freeze period covering the current time, run one tiny pipeline,
prove CI_DEPLOY_FREEZE affects job inclusion, then
delete that exact freeze ID. Do not leave broad freeze periods
behind.
14. Simulate a failed release and recover
Create a manual synthetic deployment job that verifies Pipeline B’s known artifact then exits 42. Preserve the failure. Recovery is a new deployment path that reuses the selected known artifact/commit identity. If you instead rebuild, explicitly record that it is a rebuild and compare checksums.
Required recovery evidence: original known-good checksum, failed job ID, recovery job ID, recovery deployment ID, environment state, and post-recovery verification.
15. Document the emergency exception
Write a short evidence record—not a secret-bearing comment:
Exception ID: CH18-LAB-001
Reason: synthetic recovery after deliberate failed deployment
Actor: <record GitLab username from job evidence>
Pipeline/SHA: <IDs only>
Artifact digest: <SHA-256 only>
Freeze state: simulated=true
Authorization model: disposable Free project; no protected-environment approval claim
Result: recovered synthetic environment; external side effects=none
16. Cleanup and prove cleanup
Delete the inactive schedule by exact captured ID. Delete an optional freeze period only by exact captured ID. Stop the synthetic environment. Restore/remove Chapter 18 YAML. Verify then delete the disposable branch:
git switch main
git ls-remote --heads origin refs/heads/ch18/checkpoint
# Exact disposable ref only:
git push origin --delete ch18/checkpoint
git branch -D ch18/checkpoint
# Verify schedule list no longer contains the captured ID.
glab api "projects/$PROJECT_ID/pipeline_schedules" --paginate \
--jq '.[] | {id,description,ref,active}'
# Verify freeze list contains no CH18 disposable ID (if one was created).
glab api "projects/$PROJECT_ID/freeze_periods" --paginate \
--jq '.[] | {id,freeze_start,freeze_end,cron_timezone}'
17. Final verification checklist
- Manual gate behavior and actor were predicted and verified.
- Delayed job state/timestamps were captured without treating delay as freshness.
- One stale path was safely superseded/rejected with original evidence intact.
- Schedule owner/ref/cron/active state were captured; schedule was removed.
- Freeze behavior was modeled as policy signal, not authorization.
- Rollback/recovery used or explicitly compared known artifact identity.
- No real secret, token, cloud endpoint, registry publication, protected-setting bypass, or runner registration occurred.
- Disposable server-side objects and refs were removed by exact ID/name.
18. What Chapter 18 adds to the production operating model
You now have a release state machine that accounts for people, time, ownership, freshness, and recovery. A release path can wait for a human, wait for elapsed time, recur from a schedule, respect a freeze policy, reject stale deployment, and recover to known state—all while preserving GitLab evidence rather than hiding exceptions.
Chapter 19 moves from control of a single pipeline to composition across pipelines and projects: parent-child pipelines, multi-project pipelines, trigger tokens, and downstream orchestration.
Knowledge check
Why is Pipeline A preserved after it becomes stale?
Its delayed-job ID/status/timestamps prove the supersession behavior. Deleting or retrying it would erase useful release-control evidence.
What two things must be predicted before running the checkpoint?
At minimum who/time condition permits each gate and what SHA/artifact/environment state should result. The lab also predicts stale-path behavior.
Why is the schedule inactive?
It proves schedule ownership/ref/cron state without recurring compute or accidental side effects.
Does the freeze fixture prove nobody could deploy with another credential/path?
No. It proves the pipeline’s freeze-policy behavior only. Authorization still depends on project/environment/target controls.
How do you prove the recovery is a rollback to known state?
Match the intended previous commit/artifact checksum with the recovery job/deployment evidence. If checksums differ, call it a rebuild/recovery instead.
What is the correct cleanup rule for schedules and freeze periods?
Delete only the disposable object whose exact ID/metadata you captured; never bulk-delete existing project release controls.
Checkpoint summary
You operated a complete controlled-release model without production risk: human gate, timed gate, schedule identity, freeze policy, stale-path supersession, deliberate failure, known-state recovery, and targeted cleanup. The core lesson is that release safety emerges from composing explicit identities and state transitions—not from one approval button.
Official references
- GitLab Docs — Control how jobs run
- GitLab Docs — CI/CD YAML syntax reference
- GitLab Docs — Scheduled pipelines
- GitLab Docs — Pipeline schedules API
- GitLab Docs — Deployment safety
- GitLab Docs — Deployments
- GitLab Docs — Environments
- GitLab Docs — Freeze Periods API
- GitLab Docs — Predefined CI/CD variables
- GitLab Docs — Pipeline settings
- GitLab Docs — Pipelines
- GitLab Docs — Job troubleshooting
- GitLab Docs — Resource groups
- GitLab Docs — Protected environments
- GitLab Docs — Deployment approvals
- GitLab Docs — Job token
- GitLab Docs — Roles and permissions
- GitLab 19.3 — What’s new
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.