Imagine you've just finished building a feature.
You test it locally. Everything works. You commit your changes, push them to GitHub, and then the real work begins.
Someone has to install the dependencies, run the tests, check whether the application builds correctly, create the production version, deploy it to a server, and make sure the application is actually running.
Now imagine doing all of that manually every time you make a change.
It works for a small project, but as your application grows, this process quickly becomes slow, repetitive, and error-prone.
This is where CI/CD comes in.
CI/CD is a development practice that automates the journey from writing code to delivering that code to users. Instead of manually testing, building, and deploying every change, we create a pipeline that performs these steps automatically.
A typical workflow might look like this:
Developer
↓
git push
↓
GitHub / GitLab
↓
Run Tests
↓
Build Application
↓
Build Docker Image
↓
Push Image to Registry
↓
Deploy
↓
Health Check
↓
Production 🚀
The idea is simple:
You write the code. The pipeline handles the repetitive work.
In this article, we'll break down how a modern CI/CD pipeline works from start to finish. We'll look at where Git, automated testing, Docker, container registries, and deployment platforms fit into the process—and, more importantly, how all these pieces work together.
By the end, you should be able to look at a CI/CD pipeline and understand exactly what is happening at every stage.
1. What CI/CD actually means
- CI — Continuous Integration: Automatically test and validate code whenever changes are pushed.
- CD — Continuous Delivery/Deployment: Automatically prepare or deploy validated code to an environment.
2. A realistic example
Show a developer working on:
my-app/
├── frontend/
├── backend/
├── Dockerfile
├── docker-compose.yml
└── .github/
└── workflows/
└── ci-cd.yml
Then explain what happens when they run:
git add .
git commit -m "Add authentication"
git push origin main
3. CI starts automatically
GitHub Actions, GitLab CI, Jenkins, etc. detects the push.
Example:
name: CI/CD
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Build application
run: npm run build
Then explain each step rather than just dumping YAML.
4. Where Docker fits
This is where Docker becomes particularly useful:
Source Code
↓
CI
↓
Tests
↓
Docker Build
↓
Docker Image
↓
Container Registry
↓
Production Server
↓
Docker Container
For example:
docker build -t myapp:latest .
docker push username/myapp:latest
The important concept is:
CI verifies the application. Docker packages the application. CD delivers that package to the target environment.
5. Deployment
The CD stage could SSH into a server and run:
docker pull username/myapp:latest
docker stop myapp || true
docker rm myapp || true
docker run -d \
--name myapp \
-p 80:3000 \
username/myapp:latest
Or, for a Compose-based application:
docker compose pull
docker compose up -d
6. The complete production workflow
I'd end the article with something like:
text
┌──────────────┐
│ Developer │
└──────┬───────┘
│
git push
│
▼
┌──────────────┐
│ GitHub/GitLab│
└──────┬───────┘
│
▼
┌──────────────┐
│ CI │
│ │
│ Tests │
│ Lint │
│ Build │
└──────┬───────┘
│
Success?
/ \
No Yes
│ │
▼ ▼
Stop Docker Build
│
▼
Docker Registry
│
▼
CD
│
▼
Production
│
▼
Health Check
│
▼
🚀 Live App
Top comments (1)
You clearly walk through how a simple git push can trigger a CI/CD pipeline that ultimately lands the change in production. Could you elaborate on how you handle automated rollback or versioning when a deployment fails after the pipeline passes the tests?