← Writings

CI/CD Pipelines: From Code to Production

January 2025·15 min read

Let me break down CI/CD at a high level based on what I've learned and implemented in my projects.

The Branch Strategy

Whenever we create a repo, we make certain branches — typically dev for development and prod for production. This separation is crucial for maintaining stability.

How CI Pipelines Work

Whenever a user makes changes to the dev branch, the CI (Continuous Integration) pipeline gets triggered. Here's what happens:

The CI pipeline builds the repo on an ubuntu-latest machine (usually GitHub's runners). It checks for workflows and linting errors. If the PR passes all the workflows — building the repo without errors and being free from linting and formatting issues — only then is it pushed to the dev branch.

Once every two weeks or so, all the PRs in the dev branch are reviewed. If everything is correct and working as expected, they're pushed to the prod branch.

The CD Pipeline Takes Over

This is where CD (Continuous Deployment) comes into play. Here's the flow:

  1. The repo is cloned using actions/checkout@v2 on an ubuntu-latest machine
  2. If everything builds correctly, a Docker image is created
  3. This Docker image is pushed to a Docker registry (or AWS ECR)
  4. Using Kubernetes clusters, containers are created from this image
  5. These containers are deployed to an EC2 machine and scaled up or down based on traffic

Build Workflow (build.yml)

name: Build on PR

on:
  pull_request:
    branches:
      - master
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Use Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'

      - name: Install Dependencies
        run: npm install

      - name: Generate Prisma Client
        run: npm run db:generate

      - name: Run Build
        run: npm run build

This workflow runs on every pull request. It checks out the code, installs dependencies, generates the Prisma client, and runs the build. If any step fails, the PR can't be merged.

Deploy Workflow (deploy.yml)

name: Build and Deploy to Docker Hub

on:
  push:
    branches:
      - master

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
      - name: Check Out Repo
        uses: actions/checkout@v2

      - name: Log in to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v2
        with:
          context: .
          file: ./docker/Dockerfile.user
          push: true
          tags: toovinod/cicd-pipelines:latest

      - name: Verify Pushed Image
        run: docker pull toovinod/cicd-pipelines:latest

The Big Picture

So to summarize: code changes trigger CI pipelines that test and validate. When code hits production branches, CD pipelines build Docker images, push them to registries, and deploy them to servers where they can scale based on demand.

Understanding this flow changed how I think about shipping software. Every push isn't just pushing code — it's triggering a chain of automated processes that ensure quality before anything reaches your users.