Production Capstone: Build and Govern a Secure GitLab Software Delivery Platform: Final Operational Review and Handoff
Perform the final operational review, prove critical invariants, assemble a production handoff package, document residual risks and tier constraints, and decide whether the platform is ready to operate.
Learning objectives
- Conduct a final operational review spanning architecture, access, merge governance, CI/CD, runners, variables, artifacts/registries, releases, security evidence, APIs/hooks, observability, Self-Managed recovery, and Duo governance.
- Prove at least five critical invariants using independent evidence and retain the four controlled failure/recovery records.
- Assemble a handoff package containing diagrams, role matrix, pipeline map, artifact/release evidence, runbooks, recovery objectives, tier constraints, risk register, maintenance checklist, and cleanup instructions.
- Make a production-readiness decision that distinguishes verified controls from simulated, tier-dependent, infrastructure-dependent, or residual-risk items.
- Close the GitLab course with an operating model that can be audited, maintained, recovered, and improved rather than a one-time configuration snapshot.
1. Final checkpoint scenario and acceptance contract
You are handing the Asterline delivery platform to another team.
They should be able to answer, without relying on your memory: who
can change what, how code reaches main, which pipeline
produced the release, which runner executed sensitive work, which
artifact bytes were released, which security evidence was
considered, how integrations authenticate events, what failed during
drills, how recovery works, which controls are simulated/tier-gated,
and how to clean the lab safely.
FINAL ACCEPTANCE CONTRACT
Required proof
[ ] >= 5 critical invariants independently verified
[ ] >= 4 controlled failures documented and recovered
[ ] exact commit / tag / artifact identity chain recorded
[ ] runner and credential trust boundaries documented
[ ] security evidence scope and limitations documented
[ ] API/webhook integration verification recorded
[ ] observability / audit / incident evidence references included
[ ] recovery objectives and lifecycle boundaries included
[ ] tier/offering/version assumptions time-stamped
[ ] complete cleanup / rollback procedure included
Decision values: VERIFIED | SIMULATED | TIER-DEPENDENT | NOT TESTED | FAIL
2. Build a structured handoff package
from pathlib import Path
import json, textwrap, csv
root=Path('gitlab-ch34-handoff')
for name in ['architecture','governance','pipeline','evidence','runbooks','recovery','risk','maintenance','cleanup']:
(root/name).mkdir(parents=True,exist_ok=True)
(root/'architecture'/'README.md').write_text("""# Architecture evidence\n\n- Namespace/project topology\n- Trust boundaries\n- Delivery flow\n- Evidence flow\n- External Kubernetes/Self-Managed/Duo boundaries marked optional\n""")
(root/'governance'/'roles.csv').write_text("""principal,scope,role_or_authority,reason\napp-dev,project,Developer,normal branch/MR work\nrelease-owner,project,Maintainer,release/ref governance\nplatform-owner,top-level group,Owner,namespace administration only\nrunner-operator,runner fleet,operator,execution isolation and patching\nsecurity-reviewer,security evidence,reviewer,findings/policy decisions\n""")
(root/'pipeline'/'map.md').write_text("""# Pipeline map\n\nMR/default branch -> test -> package -> security evidence\nTag -> release manifest\nSensitive deployment -> separately governed production boundary\n""")
(root/'recovery'/'objectives.json').write_text(json.dumps({
'RPO':'training example: <= 24h; production value must be business-approved',
'RTO':'training example: <= 4h; production value must be exercised',
'restore_rule':'backup is unproven until compatible disposable restore/verification',
'external_lifecycles':['configuration/secrets','object storage','runner state','external cloud/Kubernetes']
},indent=2))
(root/'maintenance'/'checklist.md').write_text("""# Maintenance checklist\n\n- Review inherited memberships and elevated roles.\n- Review branch/tag rules and paid-tier approval policy if present.\n- Review pipeline-source rules, variables, job-token allowlists, runner trust classes.\n- Review artifact/cache/registry retention and release identity.\n- Review scanner/template/component/image versions and security evidence freshness.\n- Review webhook/API failures, rate-limit behavior, and signing-token rotation.\n- Review audit/log/metric health and backup/restore test status.\n- Re-check GitLab version, deprecations, tier entitlements, and Duo/Agent status.\n""")
(root/'cleanup'/'README.md').write_text("""# Cleanup\n\n1. Export required synthetic evidence.\n2. Revoke/delete any ephemeral lab credential or webhook signing token.\n3. Detach disposable runner/integration resources.\n4. Delete synthetic packages/images/releases if policy allows.\n5. Archive/delete only the verified disposable GitLab project/group.\n6. Delete local fixture directories last.\n""")
print(root.resolve())
3. Import or create the evidence inventory
If you completed Lessons 2–4 in the same workspace, copy their
evidence files into the handoff package after reviewing them for
sensitive content. If not, use the following synthetic inventory and
label it SIMULATED. Never invent live GitLab IDs.
from pathlib import Path
import json, hashlib
root=Path('gitlab-ch34-handoff'); ev=root/'evidence'
items=[
{'id':'E-001','type':'membership','status':'SIMULATED','identity':'membership-snapshot-v1'},
{'id':'E-002','type':'merge-governance','status':'SIMULATED','identity':'main-protection+review-record'},
{'id':'E-003','type':'pipeline','status':'SIMULATED','identity':'commit-c0ffee01/pipeline-local'},
{'id':'E-004','type':'artifact','status':'VERIFIED','identity':'sha256:aaa111-fixture'},
{'id':'E-005','type':'security','status':'SIMULATED','identity':'security-report-fixture'},
{'id':'E-006','type':'release','status':'SIMULATED','identity':'v0.1.0-capstone'},
{'id':'E-007','type':'webhook','status':'VERIFIED','identity':'evt-capstone-001-signature-fixture'},
{'id':'E-008','type':'recovery','status':'SIMULATED','identity':'four-drill-recovery-record'},
]
raw=json.dumps(items,indent=2)
(ev/'inventory.json').write_text(raw)
(ev/'inventory.sha256').write_text(hashlib.sha256(raw.encode()).hexdigest()+'\n')
print(raw)
4. Prove at least five critical invariants independently
The verifier below uses explicit status fields so it cannot silently turn a simulation into a production pass. Replace fixture values only with evidence you actually collected.
from pathlib import Path
import json
root=Path('gitlab-ch34-handoff'); out=root/'evidence'/'invariant_results.json'
results=[
{'invariant':'least_privilege','status':'SIMULATED','proof':'role matrix + inherited-access drill'},
{'invariant':'reviewed_default_branch','status':'SIMULATED','proof':'branch rule + reviewer evidence; required approvals not enforced on Free'},
{'invariant':'runner_isolation','status':'SIMULATED','proof':'runner trust-class design + failure drill'},
{'invariant':'artifact_identity','status':'VERIFIED','proof':'independent checksum comparison'},
{'invariant':'release_traceability','status':'SIMULATED','proof':'tag/commit/pipeline/digest tuple fixture'},
{'invariant':'security_evidence','status':'VERIFIED','proof':'report hash + declared scope'},
{'invariant':'webhook_integrity','status':'VERIFIED','proof':'HMAC fixture + duplicate-event control'},
{'invariant':'recovery','status':'SIMULATED','proof':'backup lifecycle + recovery fixture; no live Self-Managed restore'},
]
# Acceptance: at least five invariants have evidence, but production readiness later distinguishes VERIFIED from SIMULATED.
assert sum(r['status'] in {'VERIFIED','SIMULATED'} for r in results) >= 5
out.write_text(json.dumps(results,indent=2))
for r in results: print(f"{r['status']:10} {r['invariant']:28} {r['proof']}")
5. Confirm the four failure domains and prevention owners
| Domain | Injected failure | Recovery evidence | Preventive owner |
|---|---|---|---|
| Access/governance | unexpected inherited elevated role | effective-role correction + immutable baseline | group owner |
| CI/runner/security | MR path intersected privileged/networked runner and credential | runner isolation + credential unavailability | runner/platform owner |
| Artifact/release | release digest disagreed with artifact | identity tuple restored after containment | release owner |
| Operations/integration | duplicate webhook + incomplete recovery lifecycle | idempotent event handling + separate restore inventory | integration/Self-Managed operator |
6. Perform the final operational review
| Area | What must be reviewed | Evidence / decision |
|---|---|---|
| Architecture | namespace/project ownership, external boundaries, trust arrows | architecture package |
| Access | direct/inherited roles, least privilege, offboarding path | role matrix + membership snapshot |
| Merge controls | protected refs, reviewer/approval behavior, CODEOWNERS/tier limits | branch rule + MR evidence |
| CI/CD | workflow/rules, DAG/data flow, retry/interruptibility, release gating | pipeline map + config |
| Runners | executor/isolation, tag/protection, network, lifecycle | runner trust record |
| Variables/secrets | protection/visibility/scope; no real secret in source/logs | variable metadata + incident drill |
| Artifacts/registries | artifact/package/image identity, retention, permissions | digest/evidence manifest |
| Releases/deployments | tag/commit/release/deployment linkage | release manifest |
| Security | scanner scope, report hash, vulnerability/policy limitations | security evidence + tier matrix |
| API/hooks | read-only default, pagination/rate limits, signature/idempotency | API plan + webhook verification |
| Observability/audit | request/job/time correlation, privacy-safe evidence | incident timeline / Chapter 32 practices |
| Self-Managed | backup scope, config/secrets/object storage, compatible restore | recovery inventory; simulated unless disposable instance used |
| Duo/Agentic | data classification, tool governance, approval, independent verification, credits | Chapter 33 governance handoff; optional |
7. Record the known tier/offering/version constraints in the handoff
{
"reference_date": "2026-08-22",
"gitlab_reference_release": "19.3",
"mandatory_path": "GitLab Free + local fixtures",
"constraints": {
"protected_branches": "Free/Premium/Ultimate",
"required_merge_request_approval_rules": "Premium/Ultimate",
"code_owner_enforcement": "Premium/Ultimate",
"protected_environments_and_deployment_approvals": "Premium/Ultimate",
"basic_sast_and_downloadable_report": "Free/Premium/Ultimate with supported runner",
"pipeline_secret_detection_report": "Free/Premium/Ultimate with supported runner",
"security_policies_and_dependency_scanning": "Ultimate",
"releases_and_release_evidence": "Free/Premium/Ultimate",
"self_managed_backup_restore": "Self-Managed administration",
"duo_agent_platform": "feature/status/credits/add-on/offering dependent; no mandatory AI use"
}
}
8. Build the residual risk register
from pathlib import Path
import csv
root=Path('gitlab-ch34-handoff'); p=root/'risk'/'register.csv'
rows=[
['R-01','Required approval enforcement not tested on Free path','medium','project maintainer','upgrade/paid pilot or external policy before regulated production'],
['R-02','Runner isolation represented by design/fixture','high','runner operator','test real executor/network/ephemeral lifecycle before sensitive jobs'],
['R-03','Security fixture does not equal full production scanner coverage','high','security owner','enable supported scanners and verify coverage/report ingestion'],
['R-04','No live Self-Managed restore performed','high','Self-Managed operator','schedule compatible disposable restore drill and measure RPO/RTO'],
['R-05','Kubernetes boundary not live-tested','medium','platform owner','use disposable cluster/agent before production cluster access'],
['R-06','Duo/Agent Platform not required/tested','low','AI governance owner','re-verify status/credits/privacy/tool governance before adoption'],
['R-07','GitLab feature/tier semantics can change after 19.3','medium','platform owner','review release/deprecation notes on upgrade cadence'],
]
with p.open('w',newline='') as f:
w=csv.writer(f); w.writerow(['id','risk','severity','owner','next_action']); w.writerows(rows)
print(p.read_text())
9. Create concise operating runbooks
A handoff should tell the next operator what to do when the system is not healthy. Keep runbooks action-oriented and evidence-first.
RUNBOOK — RELEASE IDENTITY MISMATCH
Trigger: tag/commit/pipeline/artifact digest disagreement
1. Stop promotion/deployment.
2. Preserve release/tag/pipeline/artifact evidence.
3. Recompute artifact digest independently.
4. Resolve tag to commit; map commit to expected pipeline.
5. Identify wrong object; do not rewrite evidence to match.
6. Rebuild/retag/correct metadata through authorized workflow.
7. Re-run security/tests as required.
8. Verify final immutable tuple and record root cause/prevention.
RUNBOOK — POSSIBLE CREDENTIAL EXPOSURE
1. Revoke/rotate/disable credential.
2. Contain affected automation/sessions.
3. Identify scope and exposure window.
4. Review audit/log/API evidence for misuse.
5. Remove source/log/history exposure where required.
6. Verify old credential unusable; issue least-privilege replacement.
RUNBOOK — PIPELINE / RUNNER TRUST FAILURE
1. Identify pipeline source and commit.
2. Freeze sensitive job/deployment path.
3. Inspect runner executor, tags/protection, network and mounts.
4. Inspect variable/job-token availability without printing values.
5. Re-route to appropriate isolated runner / narrow credentials.
6. Re-run from known commit and verify evidence.
10. Maintenance cadence after handoff
Production readiness decays. GitLab ships monthly, runner images and analyzers update, tokens expire, people change teams, storage grows, and external services change. The platform therefore needs a review cadence rather than a one-time sign-off.
| Cadence | Review |
|---|---|
| Per merge/release | diff, pipeline source, security evidence, artifact digest, release identity, deployment authorization |
| Weekly | failed/retried pipelines, runner queue/capacity, webhook errors, security backlog, storage anomalies |
| Monthly | members/roles, branch/tag rules, variables/tokens, runner versions, registry/artifact retention, API automation health |
| Per GitLab upgrade | release notes, deprecations, CI syntax/templates/components, runner compatibility, feature/tier/status changes |
| Quarterly or policy-defined | access recertification, restore drill, incident runbook exercise, risk register, RPO/RTO review |
| Before AI adoption/change | Duo/Agent feature status, credits, data processing, model/provider boundary, tool governance, human approvals |
11. Make the production-readiness decision
Use a four-way distinction. Verified means the control ran at the real intended boundary and independent evidence supports it. Simulated means the design and logic were tested with fixtures but not at the production enforcement point. Tier-dependent means the control needs an entitlement not present in the mandatory path. Not tested means no acceptable evidence exists yet.
PRODUCTION-READINESS DECISION — TRAINING CAPSTONE
VERIFIED IN LAB
- deterministic application tests
- local artifact checksum identity
- local security-evidence hash/scope
- signed-webhook HMAC verification and duplicate-event logic
- evidence-package integrity
SIMULATED / REQUIRES REAL ENVIRONMENT PROOF
- inherited membership governance at target organization scale
- runner isolation and production network boundary
- end-to-end release/deployment promotion
- Self-Managed restore and measured RPO/RTO
- Kubernetes Agent authorization/deployment path
TIER-DEPENDENT
- required MR approval rules / Code Owner enforcement
- protected-environment deployment approvals
- centralized security policies / advanced vulnerability workflows
OPTIONAL / NOT REQUIRED
- GitLab Duo or Agent Platform execution
FINAL DECISION
TRAINING OBJECTIVES: PASS when all evidence and drills are complete.
PRODUCTION DEPLOYMENT: CONDITIONAL — only after every high-risk SIMULATED/TIER-DEPENDENT item required by the target organization is verified at its real boundary.
12. Verify the handoff package itself
from pathlib import Path
import hashlib, json
root=Path('gitlab-ch34-handoff')
required=[
'architecture/README.md','governance/roles.csv','pipeline/map.md','evidence/inventory.json',
'evidence/invariant_results.json','recovery/objectives.json','risk/register.csv',
'maintenance/checklist.md','cleanup/README.md'
]
missing=[x for x in required if not (root/x).exists()]
if missing: raise SystemExit('missing: '+', '.join(missing))
manifest=[]
for rel in required:
p=root/rel
manifest.append({'path':rel,'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'bytes':p.stat().st_size})
(root/'evidence'/'handoff_manifest.json').write_text(json.dumps(manifest,indent=2))
print('HANDOFF_OK')
for x in manifest: print(x['sha256'][:12],x['bytes'],x['path'])
Hashing the handoff files does not make their claims true. It makes the reviewed handoff set identifiable so later changes can be detected. Evidence integrity and evidence correctness are separate concerns.
13. Final cleanup and rollback
Cleanup is part of the capstone, not an afterthought. Before deleting the disposable project, export the synthetic evidence you intend to keep. Revoke any ephemeral lab credential or webhook token, detach disposable runner/integration resources, remove synthetic packages/images/releases if required, then verify the namespace is truly disposable.
# Local cleanup: REVIEW the absolute path first.
pwd
ls -la gitlab-ch34-capstone gitlab-ch34-drills gitlab-ch34-handoff 2>/dev/null || true
# Delete only after the evidence package has been reviewed/exported:
# rm -rf gitlab-ch34-capstone gitlab-ch34-drills gitlab-ch34-handoff
Knowledge check
A GitLab Free project has reviewer approval comments, but merging is still technically possible without them. How should the handoff classify that control?
As a process/simulated governance control, not as required approval enforcement. Required approval rules are a paid-tier control and must be verified where the entitlement exists.
The pipeline is green and the release exists, but the artifact digest is missing. Is the release production-ready?
No. The system cannot prove exact artifact identity, so commit-to-bytes traceability is incomplete.
A protected variable is configured, but merge-request code runs on a privileged persistent runner with production network access. What is the dangerous assumption?
Assuming variable protection alone makes the execution path safe. Runner trust and pipeline-source eligibility are independent boundaries.
A secret appears in a real job log. What is the first operational action?
Revoke, rotate, or disable the credential and contain access. Log cleanup or history rewrite comes later.
A Self-Managed GitLab backup archive completed successfully. Can the recovery invariant be marked verified?
No. Backup completion does not prove restoration, compatibility, external configuration/object-storage coverage, or recovery of external runner/integration state.
A webhook signature is valid but the same event ID was already processed. What should the receiver do?
Acknowledge/handle the duplicate idempotently without repeating the consequential side effect.
Why are Premium/Ultimate controls included in a Free-compatible capstone?
Learners need to understand the production governance model and where paid enforcement would sit, while mandatory completion still uses Free controls and clearly labeled simulations.
What makes the final readiness decision trustworthy?
It states exactly what was independently verified, what was simulated or tier-dependent, which residual risks remain, who owns them, and what evidence/test must occur next.
14. Lesson summary and bridge
- You converted the entire GitLab course into an operating model with explicit resource, identity, trust, evidence, and recovery boundaries.
- At least five invariants are documented with independent evidence and four failure domains have recovery records.
- The handoff package contains architecture, roles, pipeline mapping, artifact/release evidence, runbooks, recovery objectives, tier constraints, risks, maintenance, and cleanup.
- Production readiness is conditional on verifying every high-risk simulated or tier-dependent boundary that the real organization depends on.
- The durable skill is not memorizing GitLab menus: it is operating delivery as an evidence-driven system that can explain, constrain, diagnose, and recover every consequential change.
Course complete. You have progressed from GitLab platform foundations through collaboration, CI/CD, runners, artifacts, registries, releases, DevSecOps, Kubernetes, APIs, Self-Managed operations, observability, AI governance, and finally an integrated production delivery-platform capstone. Revisit the evidence and failure drills whenever your GitLab version, offering, runner fleet, security posture, or organizational responsibilities change.
Primary sources and version notes
These lessons were finalized against current official GitLab documentation on 2026-08-22 with GitLab 19.3 as the reference release. Availability can vary by GitLab.com, Self-Managed, or Dedicated offering; Free, Premium, or Ultimate tier; namespace settings; administrator policy; runner type; and feature status. Re-check current documentation before applying a production design. This capstone closes the 34-chapter / 170-lesson GitLab curriculum. For future maintenance, re-check GitLab release notes and the linked primary documentation before reusing tier-sensitive or version-sensitive operational assumptions.
- GitLab 19.3 release
- GitLab roles and permissions
- Protected branches
- Protection rules and permissions
- Merge requests
- Merge request approvals
- Code Owners
- CI/CD pipelines
- CI/CD variables
- CI_JOB_TOKEN
- Runner security
- Job artifacts
- Caching in GitLab CI/CD
- Protected environments
- Deployment approvals
- Container Registry
- Protected container repositories
- Releases
- Release evidence
- SAST
- Pipeline secret detection
- Security policies
- REST API
- Webhooks
- GitLab agent for Kubernetes
- Audit events
- Health check endpoints
- Back up GitLab
- Restore GitLab
- GitLab Duo Agent Platform
- Agent tool governance
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.