Chapter 11Lesson 03~195 minutes

CI/CD YAML, Scripts, Images, Services, before_script, after_script, and Defaults: Configuration, Design Choices, and Tradeoffs

Choose maintainable job defaults, trusted images, service patterns, and shell portability deliberately, balancing reproducibility, isolation, compatibility, reliability, performance, governance, and cost.

DefaultsProvenancePortabilitySidecarsExecutorsTradeoffs

Learning objectives

  • Choose between defaults and explicit per-job configuration based on change radius and clarity.
  • Evaluate minimal trusted images versus convenience-heavy images using provenance, attack surface, compatibility, and maintenance criteria.
  • Choose service sidecars versus external test dependencies without confusing network convenience with production equivalence.
  • Design shell commands for portability or deliberately constrain runners when platform-specific behavior is required.
  • Use a decision table that includes maintainability, security, governance, reliability, compatibility, performance, and cost.
Availability baseline (verified 2026-08-21). The CI/CD YAML keywords taught here—default, image, services, before_script, script, and after_script—are part of core GitLab CI/CD and are available on Free, Premium, and Ultimate across GitLab.com, Self-Managed, and Dedicated. Runtime behavior still depends on the runner executor. In particular, container image/services semantics require a compatible container-capable executor; a Shell executor runs commands directly on the runner host and has materially different isolation and dependency assumptions. Hosted-compute quotas and registry availability can change, so all mandatory learning also has a CI Lint/log-fixture or local-container fallback.

1. Defaults reduce duplication but increase change radius

default is valuable when many jobs intentionally share a contract: image, runner tags, retry policy, or setup/cleanup hooks. The risk is hidden coupling. A one-line change to a default may alter every job that does not override that key, including jobs in included configuration.

A useful design question is: if this value changes, should every inheriting job change together? If yes, a default expresses policy. If no, explicit job configuration is usually clearer.

Choice Good fit Risk
default:image Most jobs share one reviewed runtime. One image update changes many jobs simultaneously.
job image A job needs a specialized runtime/toolchain. Duplication and inconsistent update cadence.
default:before_script Setup is truly common and cheap. Hidden work, network calls, or package installs on every job.
job before_script Setup belongs only to one job class. Repeated boilerplate if overused.

2. Minimal trusted image versus convenience-heavy image

A tiny image can reduce attack surface, pull time, and accidental tool availability, but it may increase setup complexity or expose differences such as BusyBox versus GNU utilities. A large “CI toolbox” image improves convenience but becomes a high-value supply-chain dependency with more packages, patching work, and hidden capabilities.

Production policy should define who builds/approves CI images, where they are hosted, how they are scanned, how digests are recorded, and how updates are promoted. A digest pins identity; it does not prove the image is trustworthy. Trust comes from provenance, review, build process, vulnerability context, and controlled promotion.

3. Service sidecar versus external integration dependency

A service sidecar is ideal for disposable tests that need an isolated database, cache, or HTTP endpoint. Its lifecycle is coupled to the job and its data can be synthetic. An external test environment may better represent shared integration behavior, but it introduces network availability, credentials, concurrency, cleanup, data governance, and potentially billing.

Dimension Service sidecar External dependency
Isolation Ephemeral per job/pipeline when executor supports it. Shared unless separately provisioned.
Credentials Often synthetic/local. Usually requires managed authentication.
Fidelity Good for protocol/component tests; not identical to production topology. Can approximate real integration more closely.
Failure modes Image pull, startup, readiness, alias/port. DNS/TLS, auth, quotas, outages, stale shared state.
Cost Runner compute and pulls. Potential service/cloud/network cost.

4. Portable shell or constrained runner: make the choice explicit

Portable scripts favor common POSIX constructs and tools that are guaranteed by the selected image. Executor-specific optimization deliberately relies on Bash, PowerShell, Windows paths, Docker socket access, GPU devices, or other platform characteristics. Both can be valid; the failure is leaving the dependency implicit.

If a job requires Bash, use an image/runner where Bash is present and document that requirement. If it requires PowerShell, constrain the runner appropriately and write PowerShell syntax. Do not assume /bin/sh, Bash arrays, PowerShell cmdlets, and Windows path quoting are interchangeable.

5. Defaults and includes create a configuration dependency graph

Included files can contribute defaults that affect jobs declared elsewhere. This is powerful for standardization but can surprise maintainers when they inspect only the local YAML. Review the merged configuration in the Pipeline Editor/CI Lint when behavior seems inconsistent with the file in front of you.

For production includes, pin the source/ref where practical, review changes, and treat remote templates/components as supply-chain dependencies. Chapter 20 goes deeper into reusable pipeline architecture; here the key principle is that resolved configuration—not one physical YAML file—is what GitLab executes.

6. Worked scenario: choose a runtime policy

A team has 20 jobs: 14 use the same static-analysis image, 4 need a database for tests, and 2 build Windows binaries. Which design is preferable?

Decision Choice Reasoning
Default image Reviewed Linux analysis image for the 14 common jobs. Reduces duplication while matching the dominant shared runtime.
DB tests Job-level image plus disposable DB service with alias/readiness. Keeps database dependency scoped to four jobs.
Windows jobs Explicit job tags/runtime and PowerShell scripts. Makes executor/platform dependency visible rather than pretending Linux portability.
Image identity Version tag in beginner lab; verified digest/internal promotion in production. Balances usability with stronger production reproducibility.
Common setup Only deterministic, cheap setup in default; specialized setup stays job-local. Avoids hidden network/install cost across all jobs.

7. Decision table: production dimensions

Dimension Questions before approving the design
Maintainability Can a reviewer understand inheritance and update scope from merged configuration?
Security Who controls the image/service; is provenance reviewed; does the executor isolate untrusted jobs?
Governance Are shared defaults/templates owned and versioned; are exceptions explicit?
Reliability Are image pulls, service readiness, network dependencies, and shell behavior deterministic?
Compatibility Does every eligible runner provide the required executor, architecture, shell, and tools?
Performance Are heavy images/services pulled unnecessarily; does default setup repeat expensive work?
Cost Does extra runner time, image transfer, external service use, or hosted compute materially increase spend?

8. Common design mistakes

  • Install the world in before_script: every job pays network/time risk. Prefer a reviewed purpose-built image when setup is stable.
  • Use latest everywhere: no repository diff is required for behavior to change.
  • Assume services equal production: sidecars are test dependencies, not topology proof.
  • Hide platform needs: Bash/PowerShell/Docker requirements should constrain runner selection visibly.
  • Copy giant defaults into each job: defeats centralized policy and creates drift.
  • Put secrets in image arguments or service commands: keep secret handling in the appropriate protected CI/CD secret mechanism; Chapter 13 covers it deeply.

Knowledge check

When is default:image a good design?

Does pinning an image digest prove the image is secure?

Why might a service sidecar be preferable to a shared external database for unit/integration tests?

What should you do when a job genuinely needs PowerShell-specific behavior?

Why inspect merged configuration when includes are used?

Summary

Good CI YAML makes coupling explicit. Use defaults for intentional shared policy, job overrides for scoped differences, reviewed and identifiable images for runtime reproducibility, services for bounded test dependencies, and explicit runner/shell constraints for platform-specific work. Optimize only after you can explain the resolved configuration and trust boundary.

Official references

Next lesson

Diagnose the runtime without erasing evidence

Lesson 4 breaks YAML quoting, image identity, service readiness, after_script assumptions, and inherited defaults, then repairs each problem at the owning layer.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.