CI/CD Pipeline Guide: GitHub Actions, Jenkins & GitLab CI Compared
What CI/CD Solves
Without CI/CD, deploying software involves manual steps: running tests locally (maybe), building artifacts by hand, uploading files to servers, and hoping nothing breaks. This process is slow, error-prone, and does not scale beyond a single developer.
CI/CD automates the entire pipeline from code push to production deployment. Every change is built, tested, and optionally deployed automatically, giving teams confidence that their main branch is always deployable and bugs are caught within minutes of introduction.
# Typical CI/CD pipeline stages:
#
# 1. TRIGGER → Push to branch or PR created
# 2. INSTALL → Install dependencies (npm ci, pip install)
# 3. LINT → Code style checks (ESLint, Prettier)
# 4. TYPE CHECK → Static analysis (TypeScript, mypy)
# 5. UNIT TESTS → Fast isolated tests
# 6. BUILD → Compile/bundle the application
# 7. INT. TESTS → Integration tests (API, database)
# 8. E2E TESTS → End-to-end browser tests (Cypress, Playwright)
# 9. DEPLOY → Ship to staging or production
# 10. NOTIFY → Slack/email notificationGitHub Actions: Full Pipeline Example
GitHub Actions is the most popular CI/CD platform for open-source and GitHub-hosted projects. Workflows are defined in YAML files inside .github/workflows/.
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-and-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
test:
runs-on: ubuntu-latest
needs: lint-and-typecheck
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.node-version }}
path: coverage/
build:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy to server
env:
SSH_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
rsync -avz -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no" \
dist/ [email protected]:/var/www/app/GitLab CI: Pipeline Configuration
GitLab CI uses a single .gitlab-ci.yml file at the repository root. It has built-in support for environments, container registries, and security scanning.
# .gitlab-ci.yml
stages:
- lint
- test
- build
- deploy
variables:
NODE_ENV: production
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
lint:
stage: lint
image: node:20-alpine
script:
- npm ci --cache .npm
- npm run lint
- npm run typecheck
test:
stage: test
image: node:20-alpine
script:
- npm ci --cache .npm
- npm test -- --coverage
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
build:
stage: build
image: node:20-alpine
script:
- npm ci --cache .npm
- npm run build
artifacts:
paths:
- dist/
deploy_production:
stage: deploy
environment:
name: production
url: https://myapp.com
script:
- apt-get update && apt-get install -y rsync
- rsync -avz dist/ [email protected]:/var/www/app/
only:
- main
when: manual # Requires manual click to deployJenkins: Declarative Pipeline
Jenkins is the most flexible CI/CD tool, with over 1,800 plugins. It runs on your own servers, giving you full control over the build environment. Jenkins pipelines are defined in a Jenkinsfile.
// Jenkinsfile (Declarative Pipeline)
pipeline {
agent {
docker { image 'node:20-alpine' }
}
environment {
NODE_ENV = 'production'
NPM_TOKEN = credentials('npm-token')
}
stages {
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Lint & Type Check') {
parallel {
stage('Lint') {
steps { sh 'npm run lint' }
}
stage('Type Check') {
steps { sh 'npm run typecheck' }
}
}
}
stage('Test') {
steps {
sh 'npm test -- --coverage'
}
post {
always {
junit 'test-results/*.xml'
publishHTML(target: [
reportDir: 'coverage/lcov-report',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Build') {
steps { sh 'npm run build' }
}
stage('Deploy') {
when { branch 'main' }
steps {
sshagent(['deploy-key']) {
sh 'rsync -avz dist/ deploy@server:/var/www/app/'
}
}
}
}
post {
failure {
slackSend channel: '#deploys',
message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
}
}
}Platform Comparison
| Feature | GitHub Actions | GitLab CI | Jenkins |
|---|---|---|---|
| Hosting | Cloud (self-hosted runners available) | Cloud + self-hosted | Self-hosted only |
| Config format | YAML | YAML | Groovy (Jenkinsfile) |
| Free tier | 2,000 min/month | 400 min/month | Free (self-hosted) |
| Marketplace | 20,000+ actions | Templates + components | 1,800+ plugins |
| Container registry | GHCR (separate) | Built-in | Plugin required |
| Learning curve | Low | Medium | High |
Caching and Speed Optimization
Pipeline speed directly impacts developer productivity. Caching dependencies is the single biggest optimization you can make. Without caching, every build downloads all packages from scratch.
# GitHub Actions: Cache node_modules
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # Automatically caches ~/.npm
# For Docker builds: Cache layers
- uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
# Parallel jobs: Run independent steps simultaneously
jobs:
lint:
runs-on: ubuntu-latest
steps: [...] # Runs in parallel with 'test'
test:
runs-on: ubuntu-latest
steps: [...] # Runs in parallel with 'lint'
build:
needs: [lint, test] # Waits for both to pass
steps: [...]
# Skip unnecessary work with path filters
on:
push:
paths:
- 'src/**'
- 'package.json'
- 'package-lock.json'
paths-ignore:
- 'docs/**'
- '*.md'CI/CD pipelines generate configuration in YAML and JSON formats. Validate your pipeline configuration files with our YAML to JSON Converter to catch syntax errors before pushing.
Deployment Strategies
How you deploy is as important as what you deploy. Different strategies offer different trade-offs between safety, speed, and complexity.
| Strategy | How It Works | Risk Level |
|---|---|---|
| Rolling | Replace instances one by one | Low |
| Blue/Green | Full parallel environment, instant switch | Very Low |
| Canary | Route 5-10% traffic to new version first | Very Low |
| Feature Flags | Deploy code, toggle features independently | Low |
| Big Bang | Replace everything at once | High |
Secrets Management in Pipelines
Never hardcode secrets in pipeline configuration files. Every CI/CD platform provides secure secret storage that injects values at runtime and masks them in logs.
# GitHub Actions: Repository Secrets
# Settings → Secrets and Variables → Actions → New repository secret
# Use in workflow:
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
# Environment-specific secrets (staging vs production):
deploy:
environment: production # Uses production secrets
env:
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
# OIDC: Passwordless cloud auth (no stored secrets!)
permissions:
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/deploy
aws-region: us-east-1
# No AWS keys stored — uses short-lived OIDC tokensFor a comprehensive guide on managing secrets across environments, see our environment variables guide.
Pipeline Best Practices
- Fail fast -- Run the quickest checks first (lint, type check). If code style fails, skip the 10-minute test suite.
- Make pipelines deterministic -- Use
npm cinotnpm install, pin action versions with SHA, and use exact Docker image tags. - Keep the main branch deployable -- Every merge to main should produce a shippable artifact. Use feature flags for incomplete features.
- Test in production-like environments -- Use the same Node version, OS, and dependency versions as production.
- Monitor pipeline health -- Track build success rate, average duration, and flaky test frequency. Alert when build times regress.
- Use branch protection rules -- Require all CI checks to pass before merging. No exceptions.
Validate Your Pipeline Config with BytePane
Convert and validate CI/CD configuration files with our YAML to JSON Converter. Schedule cron expressions for timed pipeline triggers. Compare deployment configs with the Diff Checker.
Open YAML to JSON ConverterRelated Articles
Docker Container Best Practices
Build optimized Docker images for CI/CD pipelines.
Git Branching Strategies
GitFlow, GitHub Flow, and trunk-based development.
Environment Variables Guide
Manage secrets and config across deployment environments.
Cron Jobs Tutorial
Schedule pipeline triggers and automated tasks.