CI/CD Introduction
Automate testing, building, and deployment with Continuous Integration and Continuous Deployment.
What is CI/CD?#
Continuous Integration (CI) and Continuous Deployment (CD) are practices that automate the process of getting code from your laptop to production. Instead of manually running tests, building your app, and deploying to servers, CI/CD does it automatically every time you push code.
Think of CI/CD as an assembly line for your code:
Code Push → Build → Test → Deploy
↓ ↓ ↓ ↓
Git Compile Jest Production
Every step happens automatically, consistently, and reliably. No more "it worked on my machine" or forgetting to run tests before deploying.
CI vs CD: What's the Difference?#
These terms are often used together, but they solve different problems:
Continuous Integration (CI)#
Goal: Catch bugs early by automatically testing every change.
When you push code or open a pull request, CI automatically:
- Checks out your code
- Installs dependencies
- Runs linters (code style checks)
- Runs tests (unit, integration)
- Builds the project
If any step fails, you know immediately. The bug is fresh in your mind, and you can fix it before it spreads.
Without CI: You push code, merge it, and discover a week later that something broke. By then, you've forgotten the context and have to re-investigate.
With CI: You push code, CI fails in 2 minutes, you fix it immediately.
Continuous Deployment (CD)#
Goal: Ship faster by automatically deploying code that passes CI.
Once CI verifies your code is good, CD automatically:
- Packages the application
- Deploys to staging/production servers
- Runs smoke tests to verify deployment
- Notifies the team
Without CD: You manually SSH into servers, pull code, restart services, and hope nothing breaks. This takes 30+ minutes and is error-prone.
With CD: You merge to main, and 5 minutes later it's live in production. Every. Single. Time.
| Aspect | Continuous Integration | Continuous Deployment |
|---|---|---|
| Goal | Catch bugs early | Ship faster |
| Trigger | Every commit/PR | After CI passes |
| Actions | Build, lint, test | Deploy to servers |
| Frequency | Multiple times/day | On merge to main |
Why Should You Care?#
1. Catch Bugs Early#
The cost of fixing a bug increases exponentially the longer it goes undetected:
- Bug caught in CI (minutes after writing): 5 minutes to fix
- Bug caught in code review (hours later): 30 minutes to fix
- Bug caught in production (days/weeks later): Hours or days to fix
CI runs your tests on every push. If you introduce a bug, you know within minutes.
2. Consistent Builds#
"It works on my machine" is the developer's most famous excuse. CI builds your code in a clean, consistent environment every time. If it builds in CI, it builds everywhere.
3. Faster Releases#
Manual deployments are slow and scary. With CD:
- Deployments happen in minutes, not hours
- You can deploy multiple times per day
- Small, frequent releases are less risky than big, infrequent ones
4. Better Collaboration#
When CI runs on every pull request:
- Reviewers know the code passes tests before reviewing
- Broken code can't be merged accidentally
- Team confidence in the codebase increases
5. Audit Trail#
Every deployment is logged:
- Who triggered it
- What commit was deployed
- When it happened
- Whether it succeeded or failed
This is invaluable for debugging production issues.
CI/CD Platforms Compared#
| Platform | Best For | Pricing | Learning Curve |
|---|---|---|---|
| GitHub Actions | GitHub repos | Free tier generous | Low |
| GitLab CI | GitLab repos | Built-in | Medium |
| CircleCI | Complex pipelines | Free tier | Medium |
| Jenkins | Self-hosted, enterprise | Free (self-hosted) | High |
| Vercel/Netlify | Frontend, Next.js | Free tier | Very Low |
| Railway/Render | Simple deploys | Free tier | Very Low |
Recommendation: If you're on GitHub, start with GitHub Actions. It's integrated, well-documented, and has a generous free tier.
Anatomy of a CI/CD Pipeline#
A pipeline is a series of stages that run in sequence. If any stage fails, the pipeline stops.
# Typical CI/CD pipeline stages
stages:
- install # Install dependencies
- lint # Check code style
- test # Run tests
- build # Build for production
- deploy # Deploy to servers
Let's understand each stage:
Stage 1: Install Dependencies#
- name: Install dependencies
run: npm ci # Not 'npm install'!
Why npm ci instead of npm install?
npm ciis faster (skips some checks)npm ciis reproducible (uses exact versions frompackage-lock.json)npm cifails ifpackage-lock.jsonis out of sync (good for CI)
Stage 2: Lint#
- name: Lint code
run: npm run lint
Linting catches:
- Code style violations
- Potential bugs (unused variables, missing imports)
- Formatting issues
Catching these in CI means they never reach production.
Stage 3: Test#
- name: Run tests
run: npm test
env:
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
This is the heart of CI. Your tests verify that:
- Business logic works correctly
- Edge cases are handled
- Regressions haven't been introduced
Important: Tests need access to test databases, APIs, etc. Use CI secrets for credentials.
Stage 4: Build#
- name: Build
run: npm run build
Building verifies that:
- TypeScript compiles without errors
- All imports resolve correctly
- The production bundle can be created
Stage 5: Deploy#
- name: Deploy to production
run: |
ssh user@server 'cd /app && git pull && npm ci && pm2 restart all'
This is where CD happens. After all checks pass, your code goes live.
Environment Strategy#
Never deploy directly to production. Use environments:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Development │ → │ Staging │ → │ Production │
│ (local) │ │ (preview) │ │ (live) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
npm run dev PR deployments main branch
Development#
- Your local machine
- Hot reloading, debug tools
- Test database with fake data
Staging#
- Deployed automatically on pull requests
- Mirrors production environment
- Safe to break, experiment, test
Production#
- Deployed automatically when merging to main
- Real users, real data
- Must be stable
Why this matters: You can test changes in staging before they affect real users. If staging breaks, no one cares. If production breaks, customers are angry.
Secrets Management#
Never, ever commit secrets to Git. Not API keys, not database passwords, not JWT secrets. Ever.
Use your CI/CD platform's secret management:
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
How it works:
- You add secrets in your CI/CD platform's UI (encrypted)
- During CI runs, secrets are injected as environment variables
- Secrets never appear in logs or code
Common mistake: Accidentally logging secrets. CI platforms usually redact known secrets from logs, but be careful with custom logging.
Branch Protection#
Protect your main branch from direct pushes:
main (production)
↑
└── Pull Request (with CI checks)
↑
└── feature/my-feature (development)
Rules to Enable#
- Require CI to pass - Can't merge if tests fail
- Require code review - At least one approval needed
- No direct pushes - Everything goes through PRs
- No force pushes - Prevent history rewrites
Why this matters:
- Broken code can't accidentally reach production
- Every change is reviewed
- There's a clear audit trail of who approved what
When Things Go Wrong#
CI/CD isn't magic. Things will fail. Here's how to handle it:
CI Failure#
Symptom: Pipeline goes red, PR can't be merged.
Actions:
- Click into the failed job to see logs
- Identify which step failed (lint? test? build?)
- Fix the issue locally
- Push again
Common causes:
- Tests failing (you broke something)
- Lint errors (formatting issues)
- Build errors (TypeScript errors, missing imports)
- Flaky tests (tests that randomly fail)
CD Failure#
Symptom: CI passed but deployment failed.
Actions:
- Check deployment logs
- Roll back to previous version if needed
- Fix and redeploy
Common causes:
- Server out of disk space
- Database migration failed
- Environment variable missing
- Network issues
Key Takeaways#
-
Automate everything - Tests, builds, deployments. Manual steps are error-prone.
-
Test on every commit - Catch bugs when they're fresh, not weeks later.
-
Use secrets safely - Never commit credentials. Use CI/CD secret management.
-
Protect main branch - Require CI and reviews before merging.
-
Deploy automatically - Reduce human error, increase deployment frequency.
-
Start simple - A basic CI pipeline is better than no pipeline. Add complexity as needed.
Next: Learn how to set up CI/CD with GitHub Actions in the next section.
Ready to level up your skills?
Explore more guides and tutorials to deepen your understanding and become a better developer.