CI/CD Guidelines¶
Pipeline Stages¶
Run in order; parallelize within each stage:
- Check — architecture check | lint & static analysis | secret scan | unit tests
- Build — release Docker image; build once, promote across environments
- Integration tests — test the image from stage 2
- Deploy — push to registry; manual trigger for production
Never recompile between staging and production. Inject all configuration via environment variables.
Registry Tagging¶
| Event | Tags |
|---|---|
| Pull Request | pr-<N>, sha-<short> (ephemeral) |
| Merge to main | latest, build-<N> |
Git tag v1.0.0 |
1.0.0, stable |
Makefile Targets¶
debug-build— debug symbols, no optimizations; used for development and testing.release-build— no debug symbols, speed optimizations; containerized and integration-tested.
Releases¶
A Git tag vX.Y.Z triggers the release pipeline job, which:
- Generates
RELEASE_NOTES.md(latest tag only) for the GitLab release description. - Regenerates
CHANGELOG.md(full history) and commits it back to the default branch. - Creates a GitLab release entry linked to the tag.
Required CI/CD variable: CI_ACCESS_TOKEN
Set this as a protected project CI/CD variable with write_repository scope (Project Access Token
or Personal Access Token). It allows the pipeline to push the updated CHANGELOG.md back to the
default branch. Without it, the changelog commit step is skipped and the job fails.
Documentation¶
A Git tag vX.Y.Z triggers publishing the documentation as a GitLab Pages site.
Publish on release tags only — not on every commit — so published docs always match
a stable, released version.
GitLab Pages requirements¶
- Job name must be exactly
pages— GitLab Pages ignores any other job name. - Artifacts must be in
public/— place the built site output there. expire_in: never— expiring Pages artifacts takes the site offline.- Use
needs: [build]as a quality gate so docs publish only when the binary is healthy.
Skeleton¶
pages:
stage: release
image: <your-mkdocs-image>
needs: [build]
rules:
- if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/
cache:
paths: []
script:
- mkdocs build --site-dir public
artifacts:
paths:
- public
expire_in: never
GitLab Hints¶
CI_ACCESS_TOKEN setup (required for the release job)¶
The release job pushes the updated CHANGELOG.md back to the default branch. This requires a
token with push access and matching branch protection settings.
Required setup (once per project):
- Protected Branch setting — Settings → Repository → Protected Branches →
main - "Allowed to push": Maintainers (Developers still cannot push directly and must use Merge Requests.)
- Create a Project Access Token — Settings → Access Tokens
- Role: Maintainer
- Scope:
write_repository - Add CI/CD variable — Settings → CI/CD → Variables
- Name:
CI_ACCESS_TOKEN - Value: the token
- Enable Protected
- Protect the version tag pattern — Settings → Repository → Protected Tags
- Add pattern
v*
Protected variables are only injected into pipelines triggered by protected branches or tags.
If the tag is not protected, CI_ACCESS_TOKEN is empty and the push fails with 401.
Reserved job name image:¶
image: is a reserved keyword in GitLab CI/CD — it sets the Docker executor image for a job,
not a job name. Using it as a job name causes a pipeline parse error.
Name Docker image build jobs build-image: and release-image: instead.
Overriding ENTRYPOINT for non-shell images¶
Some CI images declare a custom ENTRYPOINT (e.g. aquasec/trivy uses trivy,
gcr.io/kaniko-project/executor uses /kaniko/executor). GitLab CI injects commands
via sh -c, which fails when a custom ENTRYPOINT is set. Override it with entrypoint: [""]
to restore normal script execution.
Always use the long-form image: block for these images:
Quality report artifact convention¶
All quality gate outputs must be written to test_reports/ and exposed as CI artifacts
with when: always and expire_in: 7 days. This makes reports downloadable from any
pipeline run — including failed runs where the report is most needed.
| Sub-directory | Content |
|---|---|
test_reports/linting/ |
Linter output (checkstyle XML, text) |
test_reports/arch-test/ |
Architecture check output |
test_reports/test/ |
Unit / BDD test results (JUnit XML) |
test_reports/coverage/ |
Coverage reports (Cobertura XML) |
CI artifact block for quality gate jobs:
Coverage quality gate¶
All projects enforce a minimum line coverage of 80%. The make coverage target fails if the
threshold is not met — this works both locally and in CI.
GitLab displays the coverage percentage in pipeline views and merge request diffs by extracting it
from the job's stdout using the coverage: regex key on the coverage job. The regex is
language-specific and matches the output of the respective coverage tool:
| Language | Tool | Stdout format | GitLab regex |
|---|---|---|---|
| C++ | gcovr | TOTAL 20 17 85% |
/^TOTAL.*\s+(\d+\.?\d*%)/ |
| Go | go tool cover | total: (statements) 85.5% |
/total:\s+\(statements\)\s+(\d+\.\d+%)/ |
| Java | JaCoCo (via XML) | Line coverage: 85.0% |
/Line coverage:\s+(\d+(?:\.\d+)?%)/ |
The threshold is enforced inside make coverage, not in the CI YAML, so running make coverage
locally also fails fast on insufficient coverage.
Directives¶
- Pipeline stages in order: Check → Build → Integration Tests → Deploy; never recompile between stages
- Inject all configuration via environment variables; never bake config into images
- GitLab Pages job must be named exactly
pages; artifacts must be inpublic/;expire_in: never - Use
entrypoint: [""]for CI images with custom entrypoints (Trivy, Kaniko, etc.) - Quality reports go to
test_reports/withwhen: alwaysandexpire_in: 7 days - Enforce minimum 80% line coverage via
make coverage; fail below threshold - Never install tools in pipeline jobs — all tools must be pre-installed in the CI build image