Skip to content
Engineering Thoughts
ArticlesTopicsSeriesLearningResources
Search⌘ K

Engineering Thoughts

Field notes for engineers building systems that have to work.

AboutArchiveRSSGitHub

© 2026 Sibilesh. Built with care and open tools.

devopsintermediate2 min readJul 12, 2026

Building GitLab Pipelines That Fail Well

A practical system for fast, observable CI/CD pipelines that recover gracefully when infrastructure and dependencies misbehave.

SBy Sibilesh

On this page

  1. Start with a visible contract
  2. Separate deterministic and environmental failures
  3. Make the critical path measurable
  4. Design for cancellation

Reliable delivery is less about making every job pass and more about making failure obvious, bounded, and inexpensive. A pipeline should tell an engineer what changed, where it broke, and whether retrying is safe.

The operating principle

Optimize feedback loops before optimizing runner utilization. Human waiting time is almost always the more expensive resource.

Start with a visible contract

Every pipeline should have a small set of stages with explicit responsibilities:

A predictable pipeline
stages: [verify, build, release]
 
quality:
  stage: verify
  script:
    - go test -race ./...
    - golangci-lint run
  interruptible: true
 
image:
  stage: build
  needs: [quality]
  script: docker build --tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .

Keep the contract boring. A new contributor should understand the dependency graph in a minute.

Separate deterministic and environmental failures

Tests and static analysis are deterministic. Registry outages, rate limits, and transient network errors are environmental. Retrying both categories equally hides defects and wastes capacity.

FailureRetry?Response
Unit test assertionNoFix the change
Runner system failureYesRetry with a cap
Package checksum mismatchNoInvestigate supply chain
Registry 503YesBack off and retry

Use retry: { max: 2, when: [runner_system_failure, api_failure] } so application failures remain visible.

Make the critical path measurable

Record queue time, execution time, cache hit rate, and time to first useful failure. Those four numbers reveal whether the bottleneck is capacity, work, or pipeline shape.

flowchart LR
  Commit[Commit] --> Verify[Parallel verification]
  Verify --> Build[Immutable build]
  Build --> Scan[Security scan]
  Scan --> Release[Protected release]

Design for cancellation

Mark obsolete work as interruptible and use resource groups around stateful deployments. A newer commit should cancel work that can no longer reach production.

Production checklist
  • Protected environments require approval.
  • Build output is immutable and promoted, never rebuilt.
  • Secrets are short-lived and scoped.
  • Rollback has been tested, not merely documented.

The result is not a pipeline that never fails. It is a system that fails with enough context for the team to respond confidently.

Related tools

GitLab CI helper ↗YAML validator ↗Docker reference ↗

Keep exploring

Related field notes.

kubernetes
kubernetesbeginner1 min

The Kubernetes Signals I Check First

A repeatable debugging sequence for separating scheduling, networking, application, and capacity failures in Kubernetes.

Jun 21, 2026
go
gointermediate1 min

Structured Concurrency in Go with Context

Use cancellation, ownership, and bounded parallelism to build Go services that shut down cleanly and remain understandable.

Jul 5, 2026

Continue the discussion

Comments are enabled at deployment.

Configure the public Giscus environment variables to connect GitHub Discussions.