100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
YAML

CI/CD Pipelines for Containers

How to design continuous integration and delivery pipelines that build, test, scan, and deploy containerized applications to Kubernetes.

CI/CD & Production PracticesAdvanced12 min readJul 8, 2026
Analogies

Why Containers Change the CI/CD Pipeline

Traditional CI/CD pipelines compile code and copy artifacts to servers. Container-native pipelines instead build immutable images, tag them uniquely, push them to a registry, and then update a deployment manifest that references the new tag. This shifts the unit of delivery from 'files on a server' to 'an image plus a declarative manifest', which makes rollbacks, environment parity, and auditability far easier.

🏏

Cricket analogy: Old-school scoring involved manually copying scorecards ground to ground; the modern DRS system instead locks one verified replay (the image) with a unique ball-number tag, and every stadium screen just references that certified clip.

The Core Pipeline Stages

A typical container pipeline has five stages: build the image, run unit and static analysis tests inside or against the image, scan the image for vulnerabilities, push the image to a registry with an immutable tag (commit SHA, never 'latest'), and deploy by updating the image reference in a Kubernetes manifest or Helm values file. Each stage should fail fast and block promotion to the next stage.

🏏

Cricket analogy: A team's match-day process runs in fixed stages: nets practice (build), an internal warm-up match (test), a fitness screening (scan), naming the final XI with shirt numbers locked (immutable tag), then walking onto the field (deploy) — a failed fitness check stops selection.

yaml
# .github/workflows/build-and-deploy.yml
name: Build and Deploy
on:
  push:
    branches: [main]

jobs:
  build-test-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Set image tag
        id: vars
        run: echo "tag=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"

      - name: Log in to registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build image
        run: |
          docker build -t ghcr.io/${{ github.repository }}:${{ steps.vars.outputs.tag }} .

      - name: Run unit tests in container
        run: |
          docker run --rm ghcr.io/${{ github.repository }}:${{ steps.vars.outputs.tag }} npm test

      - name: Scan image for vulnerabilities
        uses: aquasecurity/trivy-action@0.24.0
        with:
          image-ref: ghcr.io/${{ github.repository }}:${{ steps.vars.outputs.tag }}
          severity: CRITICAL,HIGH
          exit-code: '1'

      - name: Push image
        run: docker push ghcr.io/${{ github.repository }}:${{ steps.vars.outputs.tag }}

      - name: Deploy to Kubernetes
        run: |
          kubectl set image deployment/api api=ghcr.io/${{ github.repository }}:${{ steps.vars.outputs.tag }} -n production
          kubectl rollout status deployment/api -n production --timeout=120s

Immutable Tags and GitOps Delivery

Never deploy using the 'latest' tag in production; it makes rollbacks impossible to reason about and can silently change what is running. Tag every image with a commit SHA or semantic version. Many mature pipelines split into CI (build, test, push image) and CD (a separate GitOps controller such as Argo CD or Flux watches a manifests repo and reconciles the cluster). In that model, the CI pipeline's last step is a pull request or commit that bumps the image tag in a Git-tracked manifest, not a direct kubectl apply.

🏏

Cricket analogy: Never select a player by the vague label 'in-form' without pinning who exactly that means; selectors (CI) recommend a specific named player via a memo, while the manager (a GitOps controller) only acts once that memo is officially filed in the squad list.

GitOps pattern: CI writes the new image tag into a Kubernetes manifest or Helm values.yaml in a separate 'config' repository. Argo CD/Flux detects the change and syncs the cluster automatically, giving you a full audit trail and a one-command rollback (git revert).

Testing and Scanning Before Promotion

A container-native pipeline should run at minimum: unit tests, a lint/static-analysis pass on the Dockerfile (e.g. hadolint), an image vulnerability scan (Trivy, Grype, or a registry's built-in scanner), and a smoke test that boots the container and hits a health endpoint. Gate the push and deploy steps on all of these passing; a red pipeline should never be able to reach production.

🏏

Cricket analogy: Before a player takes the field, they must pass a fitness test, a bat/gear inspection, an anti-doping screen, and a warm-up net session hitting the nets cleanly; failing any one of these means they don't get named in the XI, no exceptions.

Do not bake secrets (API keys, database passwords) into the image during the build stage. Anyone who pulls the image can extract layer contents. Inject secrets at runtime via Kubernetes Secrets, environment variables from a secret manager, or mounted volumes.

Multi-Environment Promotion

Build the image exactly once and promote the same immutable artifact through dev, staging, and production rather than rebuilding per environment. Rebuilding risks non-reproducible builds (different dependency versions resolved at different times). Promotion is just updating which environment's manifest points at that already-tested, already-scanned image tag.

🏏

Cricket analogy: A team doesn't re-recruit a fresh player for each round of the tournament — the same selected player who impressed in the warm-up moves through the group stage, semifinal, and final, only their squad-list entry gets updated as they advance.

  • Never deploy the 'latest' tag to production — use immutable tags such as the Git commit SHA.
  • Build the image once and promote the identical artifact across environments instead of rebuilding.
  • Vulnerability scanning and Dockerfile linting belong in the pipeline before the push step, not after.
  • GitOps tools like Argo CD and Flux separate CI (build/push) from CD (cluster reconciliation) for auditability.
  • Never embed secrets in image layers; inject them at deploy/runtime instead.
  • Always gate deploy steps on kubectl rollout status (or equivalent) so a failed rollout fails the pipeline.

Practice what you learned

Was this page helpful?

Topics covered

#YAML#DockerKubernetesStudyNotes#DevOps#CICDPipelinesForContainers#Pipelines#Containers#Change#Pipeline#Docker#StudyNotes#SkillVeris