Project 1: Full CI/CD Pipeline
Build a complete, production-grade CI/CD pipeline from scratch using GitHub Actions — from code commit to production deployment. This project demonstrates the full DevOps lifecycle and is a centerpiece portfolio project.
Project Overview & Architecture
- Application: A Node.js REST API with PostgreSQL and Redis
- Repository structure: Monorepo with separate frontend and backend
- Pipeline: lint → test → security scan → build Docker image → push to GHCR → deploy to staging → integration tests → deploy to production (with manual approval)
- Environments: staging (auto-deploys on main) and production (requires approval)
- Deployment target: AWS EKS cluster (provisioned with Terraform)
- Monitoring: Prometheus + Grafana post-deployment health check
Complete CI/CD Implementation
# .github/workflows/full-pipeline.yml
name: Production CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ─── Stage 1: Code Quality ───────────────────────────────────
lint-and-typecheck:
name: "Lint & 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
# ─── Stage 2: Testing ────────────────────────────────────────
test:
name: "Unit & Integration Tests"
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: testpass
ports: ["5432:5432"]
options: --health-cmd pg_isready
redis:
image: redis:7-alpine
ports: ["6379:6379"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20", cache: "npm" }
- run: npm ci
- name: Run tests with coverage
run: npm test -- --coverage
env:
DATABASE_URL: postgresql://test:testpass@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
- uses: codecov/codecov-action@v4
# ─── Stage 3: Security ───────────────────────────────────────
security:
name: "Security Scan"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20", cache: "npm" }
- run: npm ci
- name: Dependency vulnerability scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: Secret detection
uses: gitleaks/gitleaks-action@v2
# ─── Stage 4: Build & Push Image ─────────────────────────────
build:
name: "Build & Push Docker Image"
runs-on: ubuntu-latest
needs: [lint-and-typecheck, test, security]
outputs:
image-digest: ${{ steps.push.outputs.digest }}
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 }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=sha-
type=ref,event=branch
- id: push
uses: docker/build-push-action@v5
with:
push: ${{ github.ref == 'refs/heads/main' }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ─── Stage 5: Container Scan ──────────────────────────────────
container-scan:
name: "Container Security Scan"
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ needs.build.outputs.image-tag }}
severity: CRITICAL,HIGH
exit-code: "1"
# ─── Stage 6: Deploy to Staging ───────────────────────────────
deploy-staging:
name: "Deploy to Staging"
runs-on: ubuntu-latest
needs: [build, container-scan]
environment: staging
if: github.ref == 'refs/heads/main'
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_STAGING_ROLE }}
aws-region: us-east-1
- run: aws eks update-kubeconfig --name my-cluster-staging --region us-east-1
- name: Deploy to staging
run: |
kubectl set image deployment/api \
api=${{ needs.build.outputs.image-tag }} \
--namespace=staging
kubectl rollout status deployment/api --namespace=staging
- name: Run smoke tests
run: npx playwright test --project=smoke
# ─── Stage 7: Deploy to Production ────────────────────────────
deploy-production:
name: "Deploy to Production"
runs-on: ubuntu-latest
needs: deploy-staging
environment: production # Requires manual approval in GitHub UI
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_PRODUCTION_ROLE }}
aws-region: us-east-1
- run: aws eks update-kubeconfig --name my-cluster-prod --region us-east-1
- name: Deploy to production
run: |
kubectl set image deployment/api \
api=${{ needs.build.outputs.image-tag }} \
--namespace=production
kubectl rollout status deployment/api --namespace=production
- name: Notify Slack
uses: slackapi/slack-github-action@v1
with:
payload: |
{"text": "✅ Deployed ${{ needs.build.outputs.image-tag }} to production"}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}Quick Quiz
Tip
Tip
Practice Project 1 Full CICD Pipeline in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
DevOps unifies development and operations in a continuous cycle
Practice Task
Note
Practice Task — (1) Write a working example of Project 1 Full CICD Pipeline from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Common Mistake
Warning
A common mistake with Project 1 Full CICD Pipeline is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready devops code.
Key Takeaways
- Build a complete, production-grade CI/CD pipeline from scratch using GitHub Actions — from code commit to production deployment.
- Application: A Node.js REST API with PostgreSQL and Redis
- Repository structure: Monorepo with separate frontend and backend
- Pipeline: lint → test → security scan → build Docker image → push to GHCR → deploy to staging → integration tests → deploy to production (with manual approval)