CI/CD Pipeline Design¶
Scope¶
Applies to both the generated templates and the generator's own repository.
Pipeline Requirements¶
- Each pipeline stage is defined by the correctness signal it must deliver, never by a time budget (compute time or pipeline duration).
- An artifact (executable, package, OCI image) is built exactly once per commit SHA. Every test, packaging step, and later promotion reuses that one artifact instead of rebuilding it. The reason is not runtime but result fidelity: a second, independent build of the same code can differ from the first (cache state, dependency resolution, build timestamp) — at which point a green test no longer says anything about what actually gets released.
- The precondition (branch up to date and green, no direct pushes to
main), the registry tagging scheme, and the coverage gate are already specified inci_cd.mdand are not duplicated here.
Overview¶
flowchart TD
subgraph FB["Push: feature branch without MR"]
direction LR
fb_build[build] --> fb_test[unit-test]
fb_build --> fb_lint[lint]
fb_build --> fb_scan[secret-scan]
end
subgraph MR["Push: feature branch with MR"]
direction LR
mr_build[build] --> mr_test["test (unit+integration+BDD+coverage)"]
mr_build --> mr_lint[lint]
mr_build --> mr_arch[arch-test]
mr_build --> mr_scan[vuln-scan]
mr_build --> mr_docs["build docs (do not publish)"]
mr_test --> mr_pkg["build package/image from the tested artifact"]
mr_title["commit-lint against the MR title"]
end
subgraph MAIN["Merge to main"]
direction LR
m_build[build] --> m_test[full test suite]
m_test --> m_pkg["build image/package from the tested artifact"]
m_pkg --> m_scan["scan the built image/package"]
m_scan --> m_push["push, tag sha-short"]
end
subgraph TAG["Set tag vX.Y.Z"]
direction LR
t_derive["derive sha from CI_COMMIT_SHORT_SHA"] --> t_promote["re-tag image/package (no rebuild, no retest)"]
t_promote --> t_changelog["generate CHANGELOG.md"]
t_promote --> t_release["create release object"]
t_changelog --> t_docs["build and publish docs"]
end
FB -.->|"open MR"| MR
MR -.->|"squash-merge"| MAIN
MAIN -.->|"git tag vX.Y.Z"| TAG
Push to a feature branch (without MR)¶
- Goal: fast feedback on every commit, without sacrificing correctness.
- Runs: build, unit tests, lint, secret scan.
- Does not run: integration/BDD/E2E tests (test pyramid — too heavy for every commit, belong at the MR stage), docs build, package/image build, commit-lint.
- No package/image push to a registry — the artifact is not yet release-ready.
Push to a feature branch (with MR)¶
- Goal: evidence that the code is fully release-ready.
- Runs, in addition to the push stage: the full test suite (unit + integration + BDD + coverage gate), architecture check, vulnerability scan, docs build (build only, do not publish), package/image build — from the artifact already built and tested in this pipeline, not recompiled.
- Commit-lint validates
$CI_MERGE_REQUEST_TITLE, not individual commit messages:git.mdrequires Conventional Commits only for the squash commit, whose message GitLab derives from the MR title by default. Linting intermediate commits checks something the squash discards. - No package/image push to a registry.
Merge to the main branch¶
- Goal: the same signal as the MR stage, but for the actual
maincommit (a new SHA from the squash) — not a duplicate of the MR pipeline, but a check of a new state. - Runs: build, full test suite, package/image from this build.
- Additionally: the built and tested image/package is scanned for known
vulnerabilities, and only pushed to the registry, tagged
sha-<short>, once that scan passes — an intermediate state, not yet a release. The scan gates the push; it does not just report on an artifact that already shipped. - Hotfix branches (allowed directly against
mainpergit.md) go through the same two stages symmetrically: hotfix branch = feature-branch stage, merge = this stage.
Setting a tag¶
- Goal: pure promotion — no new build, no new test.
- The tag job deterministically derives
sha-<short>from$CI_COMMIT_SHORT_SHA(seeci_cd.md, ADR "ci_pipeline_design") and re-tags exactly the image/package already tested and scanned. - Additionally: generate
CHANGELOG.md, build and publish docs, create the release object. - Idempotency: re-tag and changelog push must be safely repeatable on retry (e.g. after a runner abort), without producing duplicate registry tags or duplicate release objects.
- Rollback: a broken release is corrected by setting a new tag on an older
maincommit — never by deleting or moving an existing tag (git.md).
References¶
.gitlab-ci.yml— how this repository's own pipeline implements this design.internal/templating/general/files/specs/technical_design/ADRs/ADR-004-ci_pipeline_design.md.tmpl— how generated projects implement this design.ci_cd.md— precondition, registry tagging scheme, and coverage gate referenced above.ADR-image_build_scan_push_chain.md.tmpl— the concrete build → scan → push job chain behind the scan step shown above, for project types that build a container image.