Skip to main content

Command Palette

Search for a command to run...

Basic CI Explaination

Updated
4 min read

What Even Is a CI Pipeline? (And Why You Should Care)

Before writing a single line of YAML, you need to understand what you're actually automating.

A CI (Continuous Integration) pipeline is a sequence of automated steps that runs every time your code changes. Its purpose is straightforward:

Catch problems early so broken code never reaches production.


Think of It Like Airport Security

Imagine every commit you make is like a bag at an airport.

Before it gets on the plane (production), it goes through multiple security checks. Some pass instantly, some get flagged, and some are rejected entirely.

A CI pipeline works the same way—every change is validated before it’s allowed to move forward.


The Core Mental Model

At its core, every CI pipeline follows a predictable flow:

Trigger → Checkout → Build → Test → Scan → Package → Push → Deploy → Verify

This isn’t tied to any specific tool. Whether you're using GitHub Actions, GitLab CI, or Jenkins, the idea remains the same.

Each stage exists for a reason:

It starts with a trigger (like a push or pull request) Pulls your code into a runner (checkout) Builds your application (build) Validates functionality (test) Checks for vulnerabilities (scan) Versions the artifact (package) Publishes it (push) Ships it (deploy) And finally confirms everything worked (verify)

Some pipelines skip or combine steps, but the backbone rarely changes.


Anatomy of a GitHub Actions Pipeline

Every workflow in GitHub Actions has three essential parts:

name:

on:

jobs: : runs-on: steps: - <step 1> - <step 2> What this means: name → The label you see in the Actions tab on → Defines when the pipeline runs (push, pull_request, etc.) jobs → The actual execution units

Each job runs on a fresh virtual machine. By default, jobs run in parallel unless explicitly chained using needs: .


A Real Example: Tetris App CI/CD Pipeline

Here’s a production-style pipeline for a Dockerized app:

name: Tetris CI/CD Pipeline

on: push: branches: ["main"]

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

steps:

# 1. Checkout — always first
- name: Checkout code
  uses: actions/checkout@v4

# 2. Scan source code BEFORE building
- name: Trivy FS Scan
  run: |
    docker run --rm \
      -v ${{ github.workspace }}:/app \
      aquasec/trivy fs /app \
      --severity HIGH,CRITICAL \
      --exit-code 1

# 3. Build Docker image
- name: Build image
  run: docker build -t tetris-app .

# 4. Scan the built image
- name: Trivy Image Scan
  run: |
    docker run --rm \
      -v /var/run/docker.sock:/var/run/docker.sock \
      aquasec/trivy image tetris-app \
      --severity HIGH,CRITICAL \
      --exit-code 1

# 5. Test the container
- name: Test container
  run: |
    docker run -d -p 8080:80 --name test-container tetris-app
    sleep 5
    curl -f http://localhost:8080
    docker stop test-container && docker rm test-container

# 6. Login to Docker Hub
- name: Login to Docker Hub
  run: echo "\({{ secrets.DOCKER_PASSWORD }}" | docker login -u "\){{ secrets.DOCKER_USERNAME }}" --password-stdin

# 7. Tag image
- name: Tag image
  run: |
    docker tag tetris-app \({{ secrets.DOCKER_USERNAME }}/tetris-app:\){{ github.sha }}
    docker tag tetris-app ${{ secrets.DOCKER_USERNAME }}/tetris-app:latest

# 8. Push image
- name: Push image
  run: |
    docker push \({{ secrets.DOCKER_USERNAME }}/tetris-app:\){{ github.sha }}
    docker push ${{ secrets.DOCKER_USERNAME }}/tetris-app:latest

Key Decisions Explained Why scan before building?

Scanning your source code early helps catch:

hardcoded secrets insecure configurations known vulnerabilities

It’s fast and prevents wasting time building unsafe code.


Why scan the image again?

Even if your code is clean, vulnerabilities can come from:

base images OS packages dependencies

This step acts as the real security gate.


Why use --exit-code 1?

Without it, the pipeline will continue even if vulnerabilities are found.

With it:

Any HIGH or CRITICAL issue immediately fails the pipeline.

This is what turns scanning into enforcement.


Why tag with commit SHA?

${{ github.sha }}

This ensures every build is:

unique traceable reproducible

If something breaks in production, you can map it directly to the exact commit.


Why push at the end?

Pushing is a side effect—you can’t undo it easily.

So you only push when everything else has passed:

  • build

  • scans

  • tests

This makes your pipeline a reliable safety gate.


Before You Run This Pipeline

You’ll need to configure two GitHub repository secrets:

DOCKER_USERNAME

DOCKER_PASSWORD (use an access token, not your actual password)

Navigate to:

Settings → Secrets and variables → Actions → New repository secret


Final Thought

A CI pipeline isn’t just about automation.

It’s about confidence.

When your pipeline is solid, you stop worrying about deployments breaking things—and start focusing on building.

17 views