CI/CD as a System¶
Mental model¶
CI/CD is an assembly line. Raw materials (source code) enter one end. Each station (pipeline stage) transforms, tests, or inspects the product. At the other end, a finished artifact rolls out — either to a human for approval (delivery) or straight to production (deployment).
What it looks like¶
"Automation that builds and deploys code." A YAML file in your repo that runs on every push.
What it really is¶
An event-driven system with distinct components:
event (git push)
-> trigger (webhook fires)
-> pipeline (build, test, scan)
-> artifact (immutable output)
-> promotion (dev -> staging -> prod)
-> policy (gates, approvals, rollback rules)
- CI (Continuous Integration): merge code frequently, build and test automatically on every push. Goal: catch breakage within minutes, not days.
- CD (Continuous Delivery): pipeline produces a deployable artifact. A human decides when to release.
- CD (Continuous Deployment): pipeline deploys to production automatically. No human gate.
Why it seems confusing¶
"CD" means two different things depending on who's talking. Delivery = human gate before prod. Deployment = no gate. Most teams do delivery and call it deployment.
People also conflate the pipeline (the process) with the platform (Jenkins, GitHub Actions, GitLab CI). The pipeline is the DAG of stages. The platform is just the executor.
What actually matters¶
- Build once, deploy everywhere. The artifact that passed tests in staging is the exact artifact that goes to prod. Never rebuild for a different environment.
- Immutable artifacts. A container image or binary gets a unique tag/hash. You promote the artifact, not the source.
- Fast feedback. A pipeline that takes 45 minutes to tell you about a typo is broken. Fail fast: lint and unit tests run first.
- Shift left. Move testing and security scanning earlier in the pipeline. A vulnerability caught at build time costs less than one caught in production.
flowchart LR
PUSH["git push"] --> LINT["Lint"]
LINT --> TEST["Test"]
TEST --> BUILD["Build\n(immutable artifact)"]
BUILD --> DEV["Deploy Dev"]
DEV --> STG["Deploy Staging\n(integration tests)"]
STG --> GATE{"Approval\nGate"}
GATE -->|approved| PROD["Deploy Prod"]
style PUSH fill:#888,color:#fff
style BUILD fill:#36f,color:#fff
style GATE fill:#f80,color:#fff
style PROD fill:#5a5,color:#fff
Common mistakes¶
- Rebuilding artifacts per environment instead of promoting one immutable artifact through stages.
- No pipeline at all — deploying by SSH and prayer.
- Giant monolithic pipelines with no parallelism. Split independent jobs to run concurrently.
- Skipping the plan/scan/test stages "just this once" and shipping a broken build.
- Treating CI and CD as the same thing. CI is about integration quality. CD is about release mechanics.
Small examples¶
# GitHub Actions — typical pipeline stages
name: CI/CD
on: [push]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make lint
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t app:${{ github.sha }} .
- run: docker push registry/app:${{ github.sha }}
deploy-staging:
needs: build
runs-on: ubuntu-latest
steps:
- run: helm upgrade app chart/ --set image.tag=${{ github.sha }}
# promotion to prod would be a separate job with approval gate
# Artifact promotion flow
build -> app:abc123 (image)
-> deploy to dev (automatic)
-> deploy to staging (automatic, integration tests run)
-> deploy to prod (manual approval, then automatic)
One-line summary¶
CI/CD is an event-driven pipeline that transforms source into immutable artifacts and promotes them through environments with increasing confidence.