Walk through the stages of a real CI/CD pipeline, from commit to production
A pipeline orders its checks from cheapest to most expensive, so a bad change dies as early as possible — commit, build, test, scan, package, deploy, verify, and only then release.
LEARN BY DOINGPut your intuition to work3 steps
Walk through the stages of a real CI/CD pipeline, from commit to production
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#A pipeline is a series of increasingly expensive bets that your change is safe
"CI/CD pipeline" gets thrown around as one word, but it's really answering two different questions that happen to run back to back. Continuous Integration asks "is this change good, in isolation, right now?" — build it, test it, scan it, as fast as possible, on every commit. Continuous Delivery (and its more aggressive sibling, Continuous Deployment) asks "is the system good enough to hand this change to a real user?" — deploy it somewhere real, verify it under real conditions, and only then let it reach production traffic. Delivery means every change is always ready to ship, one click away; deployment means it ships automatically the moment it passes. Most serious teams run delivery with a manual gate before the final production step, not full deployment, because a human decision point before customer-facing traffic is a deliberate risk control, not a missing automation.
#The stages, in the order that actually makes sense
The ordering is deliberate, not arbitrary: you always run the cheapest, fastest checks first. A linter and unit tests finish in seconds and catch the majority of trivial mistakes; there's no reason to spend five minutes building a container image only to discover the code doesn't compile. Security scanning goes before packaging because a critical CVE in a base image or a leaked dependency should block the pipeline before you spend time building and pushing an artifact you'll have to throw away. Deploying to staging happens only after the artifact is built once — you deploy the exact same image everywhere, never rebuild per environment, because rebuilding is how "worked in staging, broke in prod" bugs get introduced by a dependency resolving differently on a second build.
#A real pipeline, as GitHub Actions
name: CI/CDon:push:branches: [main]pull_request:branches: [main]jobs:build-and-test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- uses: actions/setup-go@v5with:go-version: "1.22"- name: Install dependenciesrun: go mod download- name: Lintrun: go vet ./...- name: Unit testsrun: go test -race -coverprofile=coverage.out ./...- name: Dependency & container scanuses: aquasecurity/trivy-action@masterwith:scan-type: fsexit-code: 1severity: CRITICAL,HIGHbuild-and-push-image:needs: build-and-testif: github.ref == 'refs/heads/main'runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Log in to registryuses: docker/login-action@v3with:registry: ghcr.iousername: ${{ github.actor }}password: ${{ secrets.GITHUB_TOKEN }}- name: Build and pushuses: docker/build-push-action@v5with:context: .push: truetags: ghcr.io/acme/api:${{ github.sha }}deploy-staging:needs: build-and-push-imageruns-on: ubuntu-latestenvironment: stagingsteps:- name: Deploy to stagingrun: ./scripts/deploy.sh staging ${{ github.sha }}- name: Smoke test stagingrun: ./scripts/smoke-test.sh https://staging.acme.internaldeploy-production:needs: deploy-stagingruns-on: ubuntu-latestenvironment:name: productionurl: https://api.acme.comsteps:- name: Deploy canary (10% of traffic)run: ./scripts/deploy.sh production ${{ github.sha }} --strategy=canary- name: Verify canary healthrun: ./scripts/verify-canary.sh- name: Promote to full rolloutrun: ./scripts/deploy.sh production ${{ github.sha }} --strategy=promote
The environment: production block is doing real work, not just documentation — GitHub lets you attach required reviewers to an environment, so the job pauses before running until someone approves it. That's the manual gate from the diagram, expressed as configuration instead of a separate approval tool. Note also that the same github.sha is threaded through every job: staging and production run the identical image, built exactly once.
#Why the deploy step is never really "one command"
A production deploy job that just swaps every instance to the new version at once is a bet with no insurance. The canary step above deploys the new version to a small slice of real traffic, watches real metrics (error rate, latency) against a baseline, and only then promotes to the rest — the same idea as a blue-green or rolling deployment, all of which exist to answer one question automatically instead of manually: "did this actually work, before everyone is exposed to it?" If the health check fails, the rollback path needs to be at least as automated as the rollout path; a rollback that requires someone to remember the previous image tag at 2 a.m. is not a rollback strategy.
🧠 Check your understanding
0/3 · 0/3 answered1. Why do security and dependency scans typically run before the container image is built and pushed, rather than after?
2. What is the key difference between Continuous Delivery and Continuous Deployment?
3. A canary deployment sends a new version to 10% of production traffic and monitors it before continuing. What is this primarily protecting against?