Resources
1Install
npx skillscat add miles990/claude-software-skills/devops-cicd Install via the SkillsCat registry.
About this skill
This skill automates software delivery through CI/CD pipelines, infrastructure as code, and deployment strategies using tools like GitHub Actions, Kubernetes, Docker, and Terraform. It addresses the need for consistent, reliable, and scalable software releases by standardizing build, test, and deployment processes. Developers and agents should use it when implementing automated workflows for building, testing, and deploying applications across different environments.
SKILL.md
DevOps & CI/CD
Overview
Practices for automating build, test, and deployment pipelines.
CI/CD Pipeline
Pipeline Stages
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Commit │ → │ Build │ → │ Test │ → │ Deploy │ → │ Release │
│ │ │ │ │ │ │ Staging │ │ Prod │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ │ │
│ │ Compile │ │ Unit │ │ │
│ │ Lint │ │ Integ │ │ │
│ │ Type │ │ E2E │ │ │
│ └─────────┘ └─────────┘ │ │
│ │ │
Trigger Manual? Approval?GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Test
run: npm run test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
- name: Build
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build
path: dist/
deploy-staging:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy to staging
run: |
# Deploy script here
echo "Deploying to staging..."
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- name: Deploy to production
run: |
echo "Deploying to production..."Matrix Builds
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: windows-latest
node: 18
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm testDeployment Strategies
Blue-Green Deployment
Before:
┌─────────────────────────────────────────┐
│ Load Balancer │
└─────────────────┬───────────────────────┘
│
┌───────┴───────┐
↓ ↓
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (idle) │
│ ACTIVE │ │ │
└───────────┘ └───────────┘
Deploy v1.1 to Green:
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (v1.1) │
│ ACTIVE │ │ testing │
└───────────┘ └───────────┘
Switch traffic:
┌───────────────┐
↓ ↓
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (v1.1) │
│ standby │ │ ACTIVE │
└───────────┘ └───────────┘Canary Deployment
# Kubernetes canary with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic to new version
- pause: { duration: 5m }
- setWeight: 30
- pause: { duration: 5m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
analysis:
templates:
- templateName: success-rate
startingStep: 2 # Start analysis at 30%
args:
- name: service-name
value: my-appRolling Update
# Kubernetes rolling update
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Can exceed replicas by 1
maxUnavailable: 0 # All must be available during update
template:
spec:
containers:
- name: app
image: my-app:v1.1
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Infrastructure as Code
Terraform
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}
# Variables
variable "environment" {
type = string
default = "production"
}
variable "instance_count" {
type = number
default = 2
}
# Resources
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main-vpc"
Environment = var.environment
}
}
resource "aws_instance" "web" {
count = var.instance_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "web-${count.index}"
}
}
# Outputs
output "instance_ips" {
value = aws_instance.web[*].public_ip
}Terraform Modules
# modules/vpc/main.tf
variable "cidr_block" {
type = string
}
variable "environment" {
type = string
}
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
tags = {
Environment = var.environment
}
}
output "vpc_id" {
value = aws_vpc.this.id
}
# Using the module
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
environment = "production"
}
resource "aws_subnet" "main" {
vpc_id = module.vpc.vpc_id
# ...
}Docker
Dockerfile Best Practices
# Use specific version tags
FROM node:20-alpine AS builder
# Set working directory
WORKDIR /app
# Copy dependency files first (layer caching)
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build application
RUN npm run build
# Production stage - smaller final image
FROM node:20-alpine AS production
WORKDIR /app
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Copy from builder
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
# Use non-root user
USER nodejs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1
# Run application
CMD ["node", "dist/index.js"]Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:Kubernetes
Basic Deployment
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:v1.0.0
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
---
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
type: ClusterIPConfigMaps and Secrets
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
---
# secret.yaml (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
database-url: cG9zdGdyZXM6Ly91c2VyOnBhc3NAaG9zdDo1NDMyL2RiGitOps
ArgoCD Application
# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/k8s-manifests.git
targetRevision: HEAD
path: apps/my-app/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueFeature Flags
// Using LaunchDarkly / Unleash pattern
interface FeatureFlags {
newCheckoutFlow: boolean;
betaFeatures: boolean;
maxUploadSize: number;
}
class FeatureFlagService {
constructor(private client: FeatureFlagClient) {}
async isEnabled(flag: keyof FeatureFlags, user?: User): Promise<boolean> {
return this.client.getBooleanValue(flag, false, {
userId: user?.id,
email: user?.email,
groups: user?.groups
});
}
}
// Usage
if (await featureFlags.isEnabled('newCheckoutFlow', user)) {
return <NewCheckoutFlow />;
} else {
return <LegacyCheckout />;
}Related Skills
- [[testing-strategies]] - CI test integration
- [[reliability-engineering]] - Deployment reliability
- [[monitoring-observability]] - Deployment monitoring