Get Started with CI/CD Pipelines: Your First Concrete Step Today
CI/CD Pipeline: The Essentials in One Article — Real Code, Diagrams, and Concrete Steps, Excerpts from a 20-Lesson Course.
The best way to learn CI CD Pipeline is by doing. This article gives you a head start with practical excerpts from a 20-lesson course — enough to get your first result today.
- Introduction to CICD
- Continuous Integration
- Code Quality
- Pipeline Optimization
- Continuous Deployment
Code Coverage and Quality Thresholds
Chapter 02 • Lesson 03 • Duration: 45 min
- Understand what code coverage is and its limitations
- Measure coverage with standard tools (pytest-cov, jest, JaCoCo)
- Define smart thresholds (line, branch, mutation)
- Block a PR if coverage drops
- Visualize reports in Codecov or SonarQube
1. What is code coverage?
Code coverage measures the percentage of lines (or branches, conditions, functions) executed by your automated tests.
| Type | Measure | Example |
|---|---|---|
| Line Coverage | % of executed lines | 80 % = 80 out of 100 lines touched |
| Branch Coverage | % of conditional branches tested | if/else, switch — both paths must pass |
| Function Coverage | % of called functions | Are all exported functions tested? |
| Statement Coverage | % of executed statements | Close to line coverage |
| Mutation Coverage | % of mutations detected by tests | More reliable but expensive (mutation testing) |
100 % coverage does NOT mean 100 % quality. You can have 100 % lines executed without verifying the results. assert is just as important as executing the code.
2. Coverage in Python with pytest-cov
pip install pytest pytest-cov # Run tests with coverage pytest --cov=mon_module --cov-report=term --cov-report=html # Console output: # Name Stmts Miss Cover # ------------------------------------- # mon_module.py 50 5 90% # ------------------------------------- # TOTAL 50 5 90% # Interactive HTML report in htmlcov/index.html
Configuration via pyproject.toml
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=term-missing --cov-report=xml --cov-fail-under=80"
[tool.coverage.run]
source = ["src"]
omit = [
"*/tests/*",
"*/migrations/*",
"*/__init__.py"
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
"if __name__ == .__main__.:",
]
fail_under = 80
show_missing = true3. Coverage in JavaScript with Jest
npm install --save-dev jest
# package.json
{
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,ts}",
"!src/**/*.d.ts",
"!src/index.ts"
],
"coverageThreshold": {
"global": {
"branches": 75,
"functions": 80,
"lines": 80,
"statements": 80
}
},
"coverageReporters": ["text", "lcov", "html", "cobertura"]
}
}
# Run
npm run test:coverage
# If threshold not met:
# Jest: "global" coverage threshold for lines (80%) not met: 73.5%
# → Build FAILS4. Coverage in Java with JaCoCo
<!-- pom.xml -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals><goal>report</goal></goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals><goal>check</goal></goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
mvn clean test
# Report in target/site/jacoco/index.html5. Defining smart thresholds
| Project type | Recommended threshold |
|---|---|
| Critical (banking, healthcare, aerospace) | 90-95 % |
| SaaS / Web app production | 80-85 % |
| Backend API | 75-85 % |
| Frontend (UI logic) | 60-70 % |
| Prototype / MVP | 40-60 % |
| Utility script | 0-30 % (often unnecessary) |
Instead of an absolute threshold, require that coverage does not decrease compared to the main branch. Codecov and SonarQube do this natively.
6. GitHub Actions integration
name: CI with Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install
run: pip install -e ".[dev]"
- name: Tests + coverage
run: pytest --cov --cov-report=xml --cov-report=term --cov-fail-under=80
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true7. Codecov: visualize and compare
codecov.yml for customization
coverage:
status:
project:
default:
target: 80%
threshold: 1%
patch:
default:
target: 90%
threshold: 5%
comment:
layout: "header, diff, flags, files"
behavior: default
require_changes: false
ignore:
- "tests/**"
- "**/__init__.py"
- "migrations/**"Managing artifacts and registries
Chapter 03 • Lesson 03 • Duration: 45 min
- Understand what an artifact and a registry are
- Store and share artifacts (zip, jar, wheel) in GitHub Actions
- Push Docker images to Docker Hub, GHCR, AWS ECR
- Manage semantic versioning of artifacts
- Set up retention rules to avoid cost explosion
1. What is an artifact?
An artifact is any file produced by a build and intended to be used later:
| Artifact type | Example | Typical registry |
|---|---|---|
| Container image | devforge-api:1.2.3 (Docker) | Docker Hub, GHCR, ECR, Quay |
| Python package | my-lib-1.0.0.whl | PyPI, AWS CodeArtifact, GitHub Packages |
| npm package | my-lib-1.0.0.tgz | npm registry, GitHub Packages |
| Java JAR | my-app-1.0.jar | Maven Central, Nexus, JFrog Artifactory |
| Compiled binary | my-cli-linux-amd64 | GitHub Releases, S3 |
| Helm chart | my-chart-1.0.0.tgz | ChartMuseum, OCI registries |
| Generic ZIP file | build-output.zip | GitHub Actions Artifacts, S3 |
2. GitHub Actions artifacts (intra-workflow)
Upload an artifact
- name: Build wheel
run: python -m build
- name: Upload wheel as artifact
uses: actions/upload-artifact@v4
with:
name: python-wheel
path: dist/*.whl
retention-days: 30Download in a subsequent job
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download wheel
uses: actions/download-artifact@v4
with:
name: python-wheel
path: ./dist
- name: Install
run: pip install dist/*.whlGitHub artifacts are free up to 500 MB per public repo and expire by default after 90 days.
3. Container registries compared
| Registry | Cost | Advantages | Disadvantages |
|---|---|---|---|
| Docker Hub | Free (public) / 5 $/user (private) | Most popular, large ecosystem | Rate limits on pulls |
| GHCR (GitHub) | Free (public) / included with GitHub | Native GitHub integration | Less known |
| AWS ECR | 0.10 $/GB/month | IAM security, auto scan, integrated with ECS/EKS | Region-specific |
| GCR / Artifact Registry | 0.10 $/GB/month | Integrated with GCP, multi-format | GCP lock-in |
| Azure ACR | Basic 5 $/month | Integrated with Azure | Azure lock-in |
| Quay.io | Free (public) / paid (private) | Strong vulnerability detection | Less support |
| Harbor (self-hosted) | Free (but ops) | No external dependency, full control | Must maintain yourself |
4. Push to Docker Hub from GitHub Actions
- name: Login 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:
context: .
push: true
tags: |
myuser/myapp:latest
myuser/myapp:${{ github.sha }}
myuser/myapp:${{ github.ref_name }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max5. Push to GHCR (GitHub Container Registry)
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}GHCR requires no additional secret: the auto-generated GITHUB_TOKEN is sufficient.
6. Push to AWS ECR
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr
aws-region: eu-west-3
- name: Login to ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: |
${{ steps.ecr.outputs.registry }}/myapp:latest
${{ steps.ecr.outputs.registry }}/myapp:${{ github.sha }}7. Semantic versioning of images
SemVer convention: MAJOR.MINOR.PATCH
| Tag | When to use it |
|---|---|
1.2.3 | Exact immutable version (strict production) |
1.2 | Latest patch of minor 1.2 |
1 | Latest minor of major 1 |
latest | Most recent (avoid in prod!) |
sha-abc1234 | Commit-based tag |
main / develop | Latest of the branch |
pr-42 | Specific to a Pull Request |
v1.2.3-rc.1 | Release candidate |
With docker/metadata-action
- name: Generate tags
id: meta
uses: docker/metadata-action@v5
with:
images: myuser/myapp
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=sha-,format=short
type=raw,value=latest,enable={{is_default_branch}}
# Automatic output according to context:
# - on main: latest, sha-abc1234, main
# - on tag v1.2.3: 1.2.3, 1.2, 1, sha-abc1234
# - on PR #42: pr-42, sha-abc1234Chapter 00.1 — CI/CD Philosophy: Why automate?
1. The world before CI/CD — The “Integration Hell”
Before adopting CI/CD practices, development teams worked in silos for weeks or months on separate branches. When they tried to integrate their code, the result was often disastrous. This phenomenon was called Integration Hell.
🔴 Before CI/CD
🟢 With CI/CD
2. The cost of a bug — The “Shift Left” rule
The “Shift Left” principle means detecting and fixing problems as early as possible in the development cycle. The later a bug is detected, the more expensive it is to fix.
CI/CD allows problems to surface at commit time, drastically reducing the cost of fixes. Automated tests, static code analysis and security scans run on every push, long before the code reaches production.
3. The three pillars: CI, CD (Delivery) and CD (Deployment)
🔵 CI — Continuous Integration
Developers integrate their code into a common branch multiple times a day. Each integration triggers an automated pipeline: compilation, unit tests, code analysis.
Goal: detect conflicts and bugs quickly.
🟡 CD — Continuous Delivery
The code is always in a deployable state. After CI, the pipeline automatically prepares a release (package, Docker image) ready to be deployed to any environment.
Goal: be able to deploy at any time, with possible human approval.
🟢 CD — Continuous Deployment
Every commit that passes the tests is automatically deployed to production, without human intervention. This is the ultimate level of automation, used by Netflix, Amazon, Google.
Goal: eliminate any delay between code and user value.
| Practice | Deployment frequency | Human approval | Required confidence level |
|---|---|---|---|
| Manual | Quarterly / Yearly | Always | Low (process) |
| CI only | Weekly / Monthly | Always | Medium (basic tests) |
| CI + CD Delivery | Daily / Weekly | Optional | High (complete tests) |
| CI + CD Deployment | Multiple times/day | Never | Very high (tests + monitoring) |
4. The CI/CD pipeline — Overview
A CI/CD pipeline is a chain of automated steps that transforms a code commit into an application deployed to production.
| Step | Description | Common tools | Typical duration |
|---|---|---|---|
| Code | Developer pushes code to Git | Git, GitHub, GitLab | — |
| Build | Compilation, dependency resolution | Maven, npm, Gradle, Docker | 1–5 min |
| Unit test | Fast tests at function level | JUnit, Jest, pytest | 1–3 min |
| Quality analysis | Code coverage, linting, security | SonarQube, ESLint, Snyk | 2–5 min |
| Package | Create a deployable artifact | Docker, JAR, ZIP, Helm | 2–10 min |
| Integration test | Tests on a complete environment | Postman, Cypress, k6 | 5–15 min |
| Deploy Staging | Deployment to test environment | Kubernetes, ECS, Heroku | 2–5 min |
| Deploy Prod | Deployment to production | Rolling, Blue/Green, Canary | 2–10 min |
| Monitor | Post-deployment monitoring and alerts | Prometheus, Grafana, Datadog | Continuous |
5. DORA metrics — Measuring DevOps performance
The DORA project (DevOps Research and Assessment) identified four key metrics that measure the effectiveness of a DevOps organization. These metrics help position your team and define improvement goals.
📈 Deployment Frequency
This article covers the most useful excerpts — the full CI CD Pipeline course (7 chapters, 20 lessons, corrected exercises and final project) takes you all the way.
./access-the-full-course free course: Mastering Claude CodeFAQ
How long does it take to learn CI CD Pipeline?
Are there any prerequisites?
Where to start concretely?
📬 Want to receive this type of guide every week? Subscribe for free — real code, zero fluff.