CI/CD is one of the most important practices in modern software delivery. It is what separates teams that deploy once a month with high stress and frequent rollbacks from teams that deploy dozens of times a day with confidence. In this part, we will understand what CI/CD really means and build real pipelines from scratch.
Continuous Integration (CI) means that every code change automatically triggers a series of automated checks: the code is built, tests are run, and the team gets immediate feedback on whether the change is safe. The goal is to catch integration problems early, when they are small and cheap to fix.
Continuous Deployment (CD) means that code that passes CI is automatically deployed to staging or production without manual intervention. The goal is to reduce the time between writing code and delivering it to users.
Together, CI/CD turns software delivery from a risky, manual event into a routine, automated process. Quality goes up, stress goes down, and teams ship faster.
A typical pipeline runs these stages in sequence:
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests with coverage
run: pytest tests/ -v --cov=. --cov-report=xml
- name: Upload coverage report
uses: codecov/codecov-action@v3
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: yourusername/myapp:${{ github.sha }},yourusername/myapp:latest
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
docker pull yourusername/myapp:latest
docker compose up -d
on:
push:
branches:
- develop # → deploys to staging
- main # → deploys to production
jobs:
deploy-staging:
if: github.ref == 'refs/heads/develop'
environment: staging
# ... staging deploy steps
deploy-production:
if: github.ref == 'refs/heads/main'
environment: production
# ... production deploy steps with approval gate
In Part 7, we will cover Infrastructure as Code with Terraform — how DevOps teams manage cloud resources through code instead of clicking through web consoles.
Defining CI/CD pipelines in code files checked into version control provides the same benefits as infrastructure as code: auditability, reproducibility, and collaborative improvement. Store pipeline definitions alongside the application code they build — this creates a clear relationship and ensures pipeline changes go through the same review process as code changes. Use reusable pipeline components where your CI/CD tool supports them — GitHub Actions reusable workflows, GitLab CI includes, Jenkins shared libraries — to avoid duplicating pipeline logic across repositories. Version your pipeline actions and dependencies explicitly rather than using floating tags that could change without notice.
CI/CD pipelines have access to production systems and sensitive credentials, making them high-value targets. Follow these security practices: store all credentials as secrets, never in pipeline code or logs. Use short-lived credentials where possible — OIDC (OpenID Connect) integration between GitHub Actions and AWS/GCP/Azure provides temporary credentials without stored secrets. Scan code for secrets before commits using tools like git-secrets or Gitleaks. Scan dependencies for vulnerabilities in the pipeline. Use separate pipelines with minimal permissions for different environments, especially production. Require manual approval gates for production deployments.
Set up a complete CI/CD pipeline for a simple web application using GitHub Actions: trigger on push to main, run linting and tests, build a Docker image, push to Docker Hub using stored secrets, and deploy to a simple hosting environment. Add a status badge to your README.md showing the CI pipeline status. Verify the pipeline runs end-to-end successfully by pushing a change and following the pipeline execution in the Actions tab.
DevOps is not a destination but a continuous journey of improvement. The practices covered here — automation, monitoring, infrastructure as code, CI/CD pipelines — are tools in service of a deeper goal: enabling teams to deliver software changes to production quickly, safely, and reliably. The measurement that matters is not which tools you use but how long it takes to go from a committed code change to running in production, and how confident you are in that process. The best DevOps teams measure their deployment frequency, lead time for changes, change failure rate, and mean time to recovery (the DORA metrics), and treat these as engineering objectives to improve over time.
Consistent application of the principles covered here, combined with ongoing learning and hands-on practice, is what separates those who understand technology conceptually from those who can build and operate real systems. The investment in depth pays dividends for years. Keep learning, keep building, and keep asking the questions that drive deeper understanding.