| name | ci-cd-architect |
| description | CI/CD pipeline design, GitHub Actions workflows, deployment automation, infrastructure as code, and release management. Use when setting up CI/CD, automating deployments, configuring build pipelines, or managing releases. |
| trigger | CI/CD, GitHub Actions, deployment pipeline, continuous integration |
CI/CD Architect
GitHub Actions Workflows
Main CI Pipeline
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm typecheck
- name: Test
run: pnpm test:ci
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t trinity-app:${{ github.sha }} .
- name: Push to registry
run: |
echo ${{ secrets.DOCKER_TOKEN }} | docker login -u ${{ secrets.DOCKER_USER }} --password-stdin
docker push trinity-app:${{ github.sha }}
Deployment Pipeline
name: Deploy
on:
workflow_run:
workflows: [CI]
branches: [main]
types: [completed]
jobs:
deploy-staging:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.STAGING_HOST }} \
"docker pull trinity-app:${{ github.sha }} && \
docker-compose up -d"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.PROD_HOST }} \
"docker pull trinity-app:${{ github.sha }} && \
docker-compose up -d"
- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
channel: '#deployments'
Docker Configuration
Multi-Stage Build
# Dockerfile
FROM node:20-alpine AS base
# Dependencies
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
# Builder
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
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
ENV PORT=3000
CMD ["node", "server.js"]
Docker Compose
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
depends_on:
- db
- redis
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
Terraform Infrastructure
# terraform/main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# ECS Cluster
resource "aws_ecs_cluster" "main" {
name = "${var.project_name}-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
# ECS Service
resource "aws_ecs_service" "app" {
name = "${var.project_name}-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
network_configuration {
subnets = aws_subnet.public[*].id
security_groups = [aws_security_group.ecs.id]
assign_public_ip = true
}
}
Release Management
Semantic Versioning Script
import { execSync } from 'child_process';
async function release(versionType: 'patch' | 'minor' | 'major') {
execSync('npm test', { stdio: 'inherit' });
const newVersion = execSync(`npm version ${versionType}`).toString().trim();
execSync('npx conventional-changelog -p angular -i CHANGELOG.md -s');
execSync(`git tag -a ${newVersion} -m "Release ${newVersion}"`);
execSync('git push origin main --tags');
console.log(`Released ${newVersion}`);
}