Skip to content

CI/CD Pipeline Fundamentals

A CI/CD pipeline is an automated sequence that takes a code change from a commit through building, testing, and releasing it, so that integration and delivery happen consistently rather than by hand.

This doc explains the vocabulary and building blocks that apply to any pipeline, whatever tool runs it (GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, and so on). The concepts are portable even though the YAML syntax differs.

For a concrete implementation, see the GitHub Actions Build & Test workflow. For the official model this terminology follows, see the GitHub Actions documentation.


CI vs. CD

Continuous Integration (CI) is the practice of merging every change into a shared mainline frequently and verifying each merge automatically by building the code and running tests. Its goal is to catch integration problems early, while they are small.

Continuous Delivery (CD) extends CI so that every change that passes CI is automatically prepared for release and can be deployed to production with a manual approval. Continuous Deployment goes one step further and releases every passing change to production automatically, with no human gate. The two share the initials "CD"; which one a team means depends on whether a human approves the final push to production.

Continuous Integration:  commit -> build -> test
Continuous Delivery:     commit -> build -> test -> deploy to staging -> (manual approval) -> production
Continuous Deployment:   commit -> build -> test -> deploy to staging -> production   (no manual gate)

Triggers

A trigger is the event that starts a pipeline run. The most common trigger is a push to a branch or a pull request, but runs can also start on a schedule (cron), on a published tag or release, or manually on demand. Choosing the right trigger keeps pipelines relevant: run the full test suite on pull requests, run deployments only on the main branch or on a tag.

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:   # manual "Run workflow" button

Jobs, Steps, and Stages

A pipeline is composed of jobs, and each job is an ordered list of steps. A step runs a single command or a prepackaged action; a job is a group of steps that run together on the same runner and share a filesystem. Steps within a job run sequentially and stop on the first failure.

Jobs run in parallel by default. A stage (sometimes called a phase) is a logical grouping of jobs — such as "build", "test", "deploy" — where one stage completes before the next begins. In tools without an explicit stage keyword, stages are expressed by declaring dependencies between jobs.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4          # a prepackaged action
      - run: dotnet build                   # a shell command

  test:
    needs: build        # "test" waits for "build" -> forms a stage boundary
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: dotnet test

Runners

A runner is the machine or container that executes a job. Hosted runners are provided and maintained by the CI service (a fresh, clean virtual machine per job); self-hosted runners are machines you own and register, used when a job needs special hardware, private network access, or licensed software. Each job starts on a clean runner, which is why any state a job needs — dependencies, build output — must be installed or restored during the run.

jobs:
  build:
    runs-on: ubuntu-latest     # hosted runner image
  deploy:
    runs-on: [self-hosted, production]   # matches a runner you registered with these labels

Artifacts

An artifact is a file or set of files produced by one job and preserved so a later job — or a person — can use it. Because each job runs on its own clean runner, passing a compiled binary or a test report from a build job to a deploy job requires uploading it as an artifact in the first job and downloading it in the second. Artifacts are also how you retain build outputs, coverage reports, or logs after a run finishes.

  build:
    steps:
      - run: dotnet publish -o ./out
      - uses: actions/upload-artifact@v4
        with:
          name: app
          path: ./out

  deploy:
    needs: build
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app

Environments and Gates

An environment represents a deployment target such as staging or production. Environments let you attach protection rules — required reviewer approval, a wait timer, or a restriction on which branches may deploy — so that reaching production is a controlled step rather than an automatic side effect. This is the mechanism that distinguishes Continuous Delivery (a gate before production) from Continuous Deployment (no gate).

  deploy-production:
    needs: test
    environment:
      name: production        # approval rules configured on this environment apply here
      url: https://example.com
    steps:
      - run: ./deploy.sh

Caching

Caching stores dependencies or intermediate output between runs so a pipeline does not redownload or rebuild everything each time. A cache is keyed on something that changes when the cached content should change — typically a hash of a lock file — so the cache is reused while dependencies are stable and rebuilt when they change. Caching is an optimization and must never hold anything a run depends on for correctness; treat it as disposable.

      - uses: actions/cache@v4
        with:
          path: ~/.nuget/packages
          key: nuget-${{ hashFiles('**/packages.lock.json') }}

Anatomy of a Typical Pipeline

Putting the pieces together, a mainstream pipeline triggers on pull requests and pushes to main, restores cached dependencies, builds once and shares the output as an artifact, runs tests in parallel, and — only on the main branch — deploys through a gated production environment.

trigger (PR / push to main)
   -> build job      (restore cache, compile, upload artifact)
   -> test job       (download nothing new; run unit + integration tests)   [depends on build]
   -> deploy job     (download artifact, deploy to gated environment)        [main branch only]

See Also