Pinning GitLab CI include and Component Versions Safely
An unpinned include: pulls whatever's on the default branch today. Here's how to pin GitLab CI components and includes by version or SHA without breaking every pipeline.
- #gitlab-cicd
- #ai
- #components
- #include
- #supply-chain
Reusable CI is great until a shared template changes under you and breaks fifty pipelines at once. The root cause is almost always an include: or component reference that isn’t pinned — it pulls whatever happens to be on the source’s default branch at the moment your pipeline runs. That makes your build non-reproducible and turns someone else’s merge into your incident. Pinning is the fix, and GitLab gives you several pinning granularities for both classic includes and the newer CI/CD components. Here’s how to choose.
The non-reproducibility trap
A project include with no ref is the usual culprit:
include:
- project: 'platform/ci-templates'
file: '/templates/build.yml'
# no ref: -> uses the default branch, whatever it is today
This config runs differently tomorrow than today if platform/ci-templates merges anything. Your pipeline’s behavior now depends on another team’s release cadence, with zero signal in your repo. The first rule of shared CI: never include without a ref.
Pinning a project include
Add a ref — a tag, a branch, or a commit SHA:
include:
- project: 'platform/ci-templates'
ref: 'v3.2.1' # a tag: readable and stable
file: '/templates/build.yml'
A tag like v3.2.1 is the sweet spot for most teams: readable, intentionally published, and stable as long as the source team doesn’t move tags (they shouldn’t). For maximum immutability — say, in a compliance-sensitive pipeline — pin to a full commit SHA, which can never change meaning:
include:
- project: 'platform/ci-templates'
ref: '9f2a1c7e4b8d3a6f0e1c2b5a7d9f4e3c8b1a2d6f'
file: '/templates/build.yml'
The trade-off is readability and update friction: SHAs are opaque and you’ll bump them often. Use tags by default; reach for SHAs where reproducibility must be provable.
Pinning CI/CD components
Components from the CI/CD Catalog use a version on the component: path. The version can be a released semver, a tag, a branch, or a SHA, and the resolution rules matter:
include:
- component: gitlab.com/platform/security-scan/sast@1.4.0
inputs:
stage: test
Pinning to @1.4.0 gives you that exact published release. You can also pin to ~latest for the most recent release, or a major-only constraint depending on how the publisher tagged — but ~latest reintroduces the moving-target problem, so I avoid it outside throwaway experiments. For anything that gates merges or deploys, pin to an exact released version.
Renovating pins without fear
Pinning isn’t “set and forget forever” — pinned-and-never-updated is how you end up three years behind on a security scanner. The healthy pattern is pin-and-renovate: pin to exact versions, and run a bot or scheduled job that opens MRs bumping those pins so updates arrive as reviewable changes, each one running through your pipeline before it lands. That gives you both reproducibility and a steady update flow — the opposite of the unpinned everything-updates-silently default.
Let AI audit your includes — and propose the pins
I point an LLM at the include section and have it find the unpinned references and suggest the right pin granularity per case:
Prompt: “Audit this GitLab CI include block. For each entry, say whether it’s pinned and to what granularity. Flag any that use the default branch implicitly. For each unpinned or branch-pinned entry, recommend a pin (tag vs SHA) and justify it based on whether the entry gates deploys. Output a table: entry | current pin | risk | recommended pin.”
The table makes the gaps obvious:
Output (excerpt):
Entry Current pin Risk Recommended platform/ci-templates build.yml none (default branch) high — non-reproducible, gates build tag v3.2.1 security-scan/sast @~latest medium — moving target on a gate @1.4.0 utils/lint @main medium tag or SHA
Verify each recommendation against what the source project actually publishes — the model may suggest a tag that doesn’t exist. Confirm the pinned ref resolves before merging. For the reusable patterns, the include and template refactor prompt, the component versioning prompt, and the broader GitLab CI/CD category are where I keep them.
The bottom line
An unpinned include: or component is a hidden dependency on someone else’s default branch, and it makes your pipeline non-reproducible and fragile to their changes. Pin every include to a ref — tags for readability, full SHAs where immutability must be provable — and pin components to exact released versions rather than ~latest. Then renovate those pins with reviewable, pipeline-tested MRs so you stay current without ever updating silently. Have AI audit the gaps and propose pins, but verify each ref exists before you trust it.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.