Continuous integration is automatically validating changes whenever developers integrate code.
The definition is unglamorous and the discipline is the point. CI is what makes a shared branch trustworthy: every change is built, linted, tested and scanned before anyone relies on it, and the result is visible on the pull request that proposed it.
This cluster builds that pipeline eight times, for eight real project types.
Start with Python CIWhat every pipeline in this cluster does
Section titled “What every pipeline in this cluster does”The stacks differ; the shape does not.
Checkout ↓Set up the toolchain ↓Restore cached dependencies ↓Install what is missing ↓Lint / format check ↓Build ↓Test ↓Upload artifacts (reports, binaries) ↓Report status to the pull requestEvery lesson works through that sequence for its ecosystem, and the differences are instructive. Some toolchains cache dependencies natively through their setup action; others need explicit cache keys. Some have a meaningful build step; others do not. Terraform and Ansible have no tests in the usual sense at all — their CI validates configuration rather than exercising code.
Prerequisites
Section titled “Prerequisites”This cluster assumes the Fundamentals cluster. Specifically:
| You should be able to | Covered in |
|---|---|
| Write a workflow from scratch | Your First Workflow |
| Filter triggers by branch and path | Events and Triggers |
| Structure work across dependent jobs | Jobs |
| Use expressions and contexts | Contexts and Expressions |
Set permissions explicitly | Least-Privilege Permissions |
You also need working knowledge of at least one of the ecosystems covered. These lessons teach CI for a stack, not the stack itself.
Learning objectives
Section titled “Learning objectives”By the end of this cluster you should be able to:
- Build a complete CI pipeline for your project’s language, from checkout to artifacts.
- Cache dependencies correctly, with keys that invalidate when they should.
- Use a matrix to test across language versions and operating systems without a combinatorial explosion.
- Publish test reports and build outputs as artifacts a reviewer can actually retrieve.
- Wire CI results into merge policy as required status checks.
- Recognise the CI patterns that are unsafe on pull requests from forks — particularly around registry credentials and infrastructure state.
The learning path
Section titled “The learning path”- Lesson 1: 01. Python CIBuild a Python CI pipeline — version matrix, pip caching, ruff, mypy, pytest with coverage, artifacts that survive failures, and a stable required check.
- Lesson 2: 02. Node.js CIBuild a Node.js CI pipeline — npm ci and lockfiles, dependency caching, version matrices, lint, type check, test with coverage, and a production build artifact.
- Lesson 3: 03. Go CIBuild a Go CI pipeline — gofmt enforcement, go vet, race-detector tests with coverage, and cross-compiled binaries produced from a single runner.
- Lesson 4: 04. Java CIBuild a Java CI pipeline with GitHub Actions — JDK matrices, Temurin distributions, Maven and Gradle dependency caching, test reports and packaged artifacts.
- Lesson 5: 05. .NET CI.NET CI on GitHub Actions: SDK version matrix, NuGet caching, restore, build, test with coverage and artifacts — a complete pipeline you can copy, with every step explained.
- Lesson 6: 06. Docker CIBuild container images in CI with GitHub Actions — Buildx, layer caching, multi-platform builds, tag derivation, and why a pull request must never push an image.
- Lesson 7: 07. Terraform CIRun Terraform in CI with GitHub Actions — fmt, validate and plan on pull requests, OIDC read-only credentials, plan output handling, and why apply does not belong here.
- Lesson 8: 08. Ansible CIValidate Ansible playbooks in CI with GitHub Actions — yamllint, ansible-lint, syntax checks, collection installs, and molecule tests that never touch production.
Choosing where to start
Section titled “Choosing where to start”The lessons are independent. Read the one matching your stack; the others are useful mainly for contrast.
| If you work in | Start at | Notable for |
|---|---|---|
| Python | Python CI | Version matrices, pip caching, coverage |
| JavaScript / TypeScript | Node.js CI | Lockfiles, npm ci, package managers |
| Go | Go CI | Vet, race detector, cross-compilation |
| Java | Java CI | Maven and Gradle, JDK matrices |
| .NET | .NET CI | Restore/build/test separation, NuGet caching |
| Containers | Docker CI | Buildx, layer caching, multi-platform |
| Terraform | Terraform CI | Plan on pull requests, and why apply is not here |
| Ansible | Ansible CI | Lint, syntax check, Molecule |
The two infrastructure lessons are worth reading even if you use neither, because they are where CI’s usual assumptions break down: there is no artifact, the “tests” mutate real systems if you are not careful, and the pull request feedback is a plan rather than a pass or fail.
What good CI feels like
Section titled “What good CI feels like”Worth naming, because “we have CI” covers a wide range of quality.
It is fast enough to wait for. Under ten minutes for the common path. Beyond that, people stop watching, start batching changes, and the feedback loop the pipeline exists to provide stops functioning.
It fails for one reason at a time. Lint and test as separate parallel jobs means both results arrive together. Combined, a formatting error hides every test result, and you fix one thing at a time across several pushes.
Its failures are legible. A failing test should say which test and why, near the top. Five hundred lines of dependency resolution before the actual error is a pipeline that technically works.
It is deterministic. A test that fails one run in twenty trains everyone to rerun rather than investigate, and the first real failure gets rerun too.
It runs the same thing locally. If CI runs make test and a developer can run make test, the
pipeline is reproducible. If CI runs forty lines of inline shell, it is not.
Those five properties matter more than which actions you use, and the lessons in this cluster are written to produce them.
Caching, briefly
Section titled “Caching, briefly”Every pipeline here caches dependencies, because the alternative is paying the download cost on every run of every branch forever.
Most setup actions now cache natively:
- uses: actions/setup-node@v7 with: node-version: "22" cache: npm
- uses: actions/setup-python@v7 with: python-version: "3.13" cache: pipThat is one line and it is preferable to configuring actions/cache by hand, because the action knows
where its ecosystem stores things and what the cache key should depend on.
Where you do need explicit control — a build cache, a tool that no setup action covers — Caching covers keys, restore keys and invalidation. The critical property to remember is that a cache may vanish at any time: a pipeline that fails on a cache miss is broken, not optimised.
CI and merge policy
Section titled “CI and merge policy”A workflow’s result becomes useful when it gates merging.
Each job reports a status check named after it. Making that check required — through branch protection or a ruleset — means a pull request cannot merge until it passes.
Two consequences worth internalising now:
The check name is the job name. Renaming a job renames the check, and the old name stays required while never reporting again. Every pull request then blocks on something that no longer exists.
Required checks must be able to run. A check gated behind a path filter will not report on pull requests that do not touch those paths — so those pull requests wait forever. Either do not require it, or have it report a trivially successful result when skipped.
If the repository uses a merge queue, workflows must also
handle the merge_group event or the queue stalls.
What each lesson adds
Section titled “What each lesson adds”The eight pipelines share a shape, and each contributes something the others do not:
Python introduces version matrices and coverage reporting.
Node.js covers lockfiles, and why npm ci differs from npm install in CI.
Go covers vet, the race detector, and cross-compiling from one runner.
Java covers two build systems — Maven and Gradle — with different caching characteristics.
.NET covers the restore/build/test separation and why it matters for caching.
Docker covers Buildx, layer caching, multi-platform images, and the rule about never pushing from an untrusted pull request.
Terraform covers planning on pull requests, and why apply deliberately does not appear here.
Ansible covers linting and syntax checking where there is no artifact and no conventional test suite.
Reading the one for your stack is sufficient. Reading the Docker and Terraform lessons as well is worth the time regardless, because they are where CI’s usual assumptions stop holding — one produces an artifact that must not escape, the other has no artifact at all.
Security in CI
Section titled “Security in CI”CI runs on every pull request, including from strangers. That is the point and it is also the risk.
Three rules apply to every pipeline in this cluster:
Pull request workflows get no secrets by default. Workflows triggered by pull_request from a
fork run with a read-only token and no access to repository secrets. This is deliberate — otherwise
anyone could open a pull request that printed your credentials.
Never push anything from an untrusted pull request. Building a container image on a pull request is fine. Pushing it to a registry is not, because the pull request’s contents decide what gets pushed.
Treat pull request metadata as attacker-controlled. A branch name or pull request title interpolated into a shell command is a script injection. Workflow Security covers this properly, and every lesson here follows its guidance.
A note on the examples
Section titled “A note on the examples”Each lesson builds a workflow for a small but realistic project rather than a hello-world. That means dependency files, a test suite, and the awkward parts — a lockfile that must be respected, a coverage report that should survive a failure, a matrix that should not run six times what it needs to run once.
The workflows are meant to be adapted rather than copied wholesale. Where a choice is a matter of taste rather than correctness, the lesson says so.
Begin: Python CI with GitHub Actions