🔄 Building a Complete CI/CD Pipeline
Learn how to build a professional CI/CD pipeline that takes your code from commit to production automatically.
Pipeline Overview
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Code │───▶│ Build │───▶│ Test │───▶│ Deploy │───▶│ Monitor │
│ Commit │ │ │ │ │ │ │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘Tools We’ll Use
- GitHub - Source control
- GitHub Actions - CI/CD automation
- Docker - Containerization
- Docker Hub or GHCR - Image registry
- Kubernetes or Docker Compose - Deployment
Step 1: Repository Setup
# Initialize repository
git init
git add .
git commit -m "Initial commit"
git remote add origin git@github.com:username/project.git
git push -u origin mainStep 2: Dockerfile
# Multi-stage build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production image
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]Step 3: CI Workflow
Create .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint:
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
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
build:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
context: .
push: false
tags: myapp:test
cache-from: type=gha
cache-to: type=gha,mode=maxStep 4: CD Workflow
Create .github/workflows/cd.yml:
name: CD
on:
push:
branches: [main]
tags: ["v*"]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=sha
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
deploy-staging:
needs: build-and-push
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to Staging
run: |
echo "Deploying to staging..."
# kubectl apply or docker compose commands
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Deploy to Production
run: |
echo "Deploying to production..."Step 5: Environment Protection
Configure environments in GitHub:
- Go to Settings → Environments
- Create
stagingandproduction - Add required reviewers for production
- Set deployment branch rules
Pipeline Best Practices
| Practice | Benefit |
|---|---|
| Fast feedback | Fail early, fix quickly |
| Parallel jobs | Faster pipeline |
| Caching | Reduced build time |
| Secrets management | Security |
| Environment protection | Controlled deployments |
Monitoring Deployments
Add health checks and notifications:
- name: Health Check
run: |
for i in {1..30}; do
if curl -s https://staging.example.com/health | grep -q "ok"; then
echo "Health check passed!"
exit 0
fi
sleep 10
done
echo "Health check failed!"
exit 1
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}🎯 Goal: Every push should be deployable. Keep your main branch always releasable!
Published: December 15, 2024