Devops

CI/CD Pipeline Patterns

πŸ“‹ At a Glance

AspectDetails
Difficulty🟠 Advanced
PrerequisitesPart 3 (Build Process), Part 6 (Multi-Stage)
CI PlatformsGitHub Actions, GitLab CI, Jenkins
Time Investment28 minutes read + 90 minutes practice
Payoff10x faster builds, secure deployments, automated releases

🎯 What You'll Learn

After this article, you'll be able to:

  1. Optimize CI/CD build speed with intelligent caching strategies
  2. Build multi-architecture images for ARM and x86
  3. Implement security scanning in your pipeline
  4. Tag and version images properly for production
  5. Deploy with confidence using rolling updates and rollbacks

πŸ”₯ Production Story: The 45-Minute Build

The Setup: A team's CI pipeline took 45 minutes per build. Developers avoided pushing because feedback was too slow. PRs piled up.
The Pipeline:
YAML(3 lines)
Code
Loading syntax highlighter...
The Investigation:
Step 1/15 : FROM node:20
 ---> Pulling image (30 seconds)
Step 5/15 : RUN npm ci
 ---> Installing all packages (15 minutes)
Step 10/15 : RUN npm run build
 ---> Building (10 minutes)
Root Causes:
  1. No Docker layer caching between builds
  2. No dependency caching (npm ci every time)
  3. Single-stage build (dev dependencies in production)
  4. Building same image for test and deploy
The Fix:
YAML(12 lines)
Code
Loading syntax highlighter...
Results:
  • First build: 15 minutes (vs 45)
  • Cached builds: 3 minutes
  • 90% reduction in CI time

🧠 Mental Model: CI/CD Pipeline Stages

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    CI/CD PIPELINE FLOW                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚   β”‚                      BUILD                           β”‚      β”‚
β”‚   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚      β”‚
β”‚   β”‚  β”‚  Lint   β”‚β†’ β”‚  Test   β”‚β†’ β”‚  Build  β”‚β†’ β”‚  Scan   β”‚  β”‚      β”‚
β”‚   β”‚  β”‚ (fast)  β”‚  β”‚  unit   β”‚  β”‚  image  β”‚  β”‚security β”‚  β”‚      β”‚
β”‚   β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚      β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                           ↓                                     β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚   β”‚                      TEST                            β”‚      β”‚
β”‚   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚      β”‚
β”‚   β”‚  β”‚  E2E    β”‚β†’ β”‚  Int    β”‚β†’ β”‚  Perf   β”‚               β”‚      β”‚
β”‚   β”‚  β”‚  tests  β”‚  β”‚  tests  β”‚  β”‚  tests  β”‚               β”‚      β”‚
β”‚   β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚      β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                           ↓                                     β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚   β”‚                     RELEASE                          β”‚      β”‚
β”‚   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚      β”‚
β”‚   β”‚  β”‚  Tag    β”‚β†’ β”‚  Push   β”‚β†’ β”‚ Deploy  β”‚               β”‚      β”‚
β”‚   β”‚  β”‚ version β”‚  β”‚  to reg β”‚  β”‚  to env β”‚               β”‚      β”‚
β”‚   β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚      β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                                                                 β”‚
β”‚   CACHING STRATEGY                                              β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚   β”‚  Layer Cache ─→ Dependency Cache ─→ Build Cache      β”‚      β”‚
β”‚   β”‚                                                      β”‚      β”‚
β”‚   β”‚  Registry      npm/maven         BuildKit            β”‚      β”‚
β”‚   β”‚  cache-from    cache action      --mount=type=cache  β”‚      β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”¬ Deep Dive

1. GitHub Actions - Complete Pipeline

YAML(188 lines)
Code
Loading syntax highlighter...

2. Caching Strategies

Layer Caching with BuildKit:
YAML(22 lines)
Code
Loading syntax highlighter...
Dependency Caching in Dockerfile:
DOCKERFILE(35 lines)
Code
Loading syntax highlighter...
Maven/Gradle Caching:
DOCKERFILE(7 lines)
Code
Loading syntax highlighter...

3. Multi-Architecture Builds

Building for ARM and x86:
YAML(9 lines)
Code
Loading syntax highlighter...
Dockerfile Considerations for Multi-Arch:
DOCKERFILE(20 lines)
Code
Loading syntax highlighter...
GitLab CI Multi-Arch:
YAML(14 lines)
Code
Loading syntax highlighter...

4. Image Tagging Strategies

Semantic Versioning:
YAML(21 lines)
Code
Loading syntax highlighter...
Generated Tags Example:
EventTags Generated
Push to developdevelop, sha-abc1234
PR #42pr-42
Tag v1.2.31.2.3, 1.2, 1, latest, sha-abc1234
Manual Tagging Script:
BASH(21 lines)
Code
Loading syntax highlighter...

5. Security Scanning

Trivy Scanner in Pipeline:
YAML(24 lines)
Code
Loading syntax highlighter...
Snyk Integration:
YAML(8 lines)
Code
Loading syntax highlighter...
Scan During Build:
DOCKERFILE(14 lines)
Code
Loading syntax highlighter...

6. GitLab CI Complete Pipeline

YAML(129 lines)
Code
Loading syntax highlighter...

7. Jenkins Pipeline

GROOVY(79 lines)
Code
Loading syntax highlighter...

8. Build Optimization Checklist

YAML(71 lines)
Code
Loading syntax highlighter...

⚠️ Common Mistakes

Mistake 1: No Caching Strategy

YAML(8 lines)
Code
Loading syntax highlighter...

Mistake 2: Building Unnecessarily

YAML(11 lines)
Code
Loading syntax highlighter...

Mistake 3: Using Mutable Tags

YAML(6 lines)
Code
Loading syntax highlighter...

Mistake 4: No Security Scanning

YAML(9 lines)
Code
Loading syntax highlighter...

Mistake 5: Secrets in Build Args

YAML(5 lines)
Code
Loading syntax highlighter...
DOCKERFILE(3 lines)
Code
Loading syntax highlighter...

πŸ› Debug This

CI builds are slow, even with caching enabled:

YAML(6 lines)
Code
Loading syntax highlighter...
DOCKERFILE(6 lines)
Code
Loading syntax highlighter...
Build always runs npm ci even when only source files changed. Why?
Click to reveal analysis
Problem: COPY . . includes everything, including source files. When any file changes, the cache invalidates, forcing npm ci to run again.
Fix: Order layers by change frequency:
DOCKERFILE(16 lines)
Code
Loading syntax highlighter...
Additional optimization: Add .dockerignore:
node_modules
.git
*.md
.env*
Now npm ci only runs when package.json or package-lock.json changes.

πŸ’» Exercises

Exercise 1: Basic CI Pipeline

Create a GitHub Actions workflow that:

  • Runs lint and tests
  • Builds Docker image
  • Pushes to GitHub Container Registry
  • Uses caching

Exercise 2: Multi-Arch Build

Modify your pipeline to:

  • Build for both AMD64 and ARM64
  • Use QEMU for cross-platform builds
  • Push multi-arch manifest

Exercise 3: Security Scanning

Add security scanning that:

  • Scans for HIGH and CRITICAL vulnerabilities
  • Fails the build on findings
  • Uploads results to GitHub Security tab

Exercise 4: Tagging Strategy

Implement automatic tagging:

  • Branch builds: branch-name, sha-xxx
  • Tag builds: 1.2.3, 1.2, 1, latest
  • PR builds: pr-123

Exercise 5: Complete Pipeline

Create a full pipeline with:

  • Lint, unit tests, integration tests
  • Docker build with caching
  • Security scan
  • Deploy to staging (on develop)
  • Deploy to production (on tag, manual approval)

🎀 Interview Questions

Q1: How do you optimize Docker build times in CI/CD?

Answer: Multiple strategies at different levels:
  1. BuildKit Layer Caching:
YAML(4 lines)
Code
Loading syntax highlighter...
  1. Dockerfile Layer Ordering:
DOCKERFILE(5 lines)
Code
Loading syntax highlighter...
  1. BuildKit Cache Mounts:
DOCKERFILE
Code
Loading syntax highlighter...
  1. Parallelization:
  • Multi-stage builds run in parallel when possible
  • Split independent tests into parallel jobs
  1. Build Context Optimization:
  • Use .dockerignore to exclude unnecessary files
  • Only copy what's needed

Q2: How do you handle secrets in CI/CD Docker builds?

Answer: Never expose secrets in the image or build args:
BuildKit Secrets (recommended):
YAML(9 lines)
Code
Loading syntax highlighter...
Build-Time Only:
DOCKERFILE(8 lines)
Code
Loading syntax highlighter...
Runtime Injection:
YAML(5 lines)
Code
Loading syntax highlighter...

Q3: Explain how you would implement a multi-architecture build pipeline.

Answer:
  1. Setup QEMU and BuildX:
YAML(2 lines)
Code
Loading syntax highlighter...
  1. Build with platforms:
YAML(4 lines)
Code
Loading syntax highlighter...
  1. Dockerfile Considerations:
DOCKERFILE(10 lines)
Code
Loading syntax highlighter...
  1. Result: Single tag (myapp:1.0) contains manifest with both architectures. Docker automatically pulls the right one for the host.

Q4: How do you integrate security scanning into a Docker CI/CD pipeline?

Answer: Multiple layers of security:
  1. Image Scanning (vulnerabilities in dependencies):
YAML(5 lines)
Code
Loading syntax highlighter...
  1. Dockerfile Linting (configuration issues):
YAML(3 lines)
Code
Loading syntax highlighter...
  1. Secret Scanning (leaked credentials):
YAML(3 lines)
Code
Loading syntax highlighter...
  1. SBOM Generation (software bill of materials):
YAML(3 lines)
Code
Loading syntax highlighter...
  1. Policy Enforcement:
  • Fail build on CRITICAL vulnerabilities
  • Allow HIGH with exceptions
  • Block images without security scan

Q5: What's your strategy for image tagging in a CI/CD pipeline?

Answer: Tagging strategy depends on use case:
Branch Builds (development):
myapp:develop
myapp:feature-xyz
myapp:sha-abc1234
Release Builds (production):
myapp:1.2.3        # Exact version
myapp:1.2          # Latest patch
myapp:1            # Latest minor
myapp:latest       # Current stable
Implementation:
YAML(7 lines)
Code
Loading syntax highlighter...
Best Practices:
  • Never rely solely on latest for deployments
  • Use immutable tags (SHA) for traceability
  • Semantic versions for human readability
  • Keep audit trail of what's deployed where

πŸ“ Summary & Key Takeaways

Caching Strategy

  1. Use BuildKit cache (type=gha or type=registry)
  2. Order Dockerfile layers by change frequency
  3. Use cache mounts for package managers
  4. Optimize build context with .dockerignore

Tagging Strategy

  • Branch: branch-name, sha-xxx
  • Release: 1.2.3, 1.2, 1, latest
  • Always use SHA for deployment tracking

Security

  • Scan images before pushing
  • Use BuildKit secrets for sensitive data
  • Generate SBOMs for compliance

Pipeline Stages

Lint β†’ Test β†’ Build β†’ Scan β†’ Deploy

πŸ“‹ Quick Reference

YAML(19 lines)
Code
Loading syntax highlighter...

πŸ“… Review Schedule

  • Day 1: Set up basic build and push pipeline
  • Day 3: Add caching strategies
  • Day 7: Implement security scanning
  • Day 14: Build multi-arch images
  • Day 30: Optimize and add deployment stages

πŸ“š Series Navigation