devops-deployer
SKILL.md
DevOps & Deployment
Expert guidance for setting up robust deployment pipelines and infrastructure.
When to Use This Skill
- Creating CI/CD pipelines
- Writing Dockerfiles
- Setting up container orchestration
- Managing environment configurations
- Handling secrets securely
- Automating deployments
- Setting up monitoring and logging
- Infrastructure as code
CI/CD Pipeline Design
Pipeline Stages
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Build │──▶│ Test │──▶│ Analyze │──▶│ Deploy │──▶│ Verify │
│ │ │ │ │ │ │ Staging │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│
▼
┌─────────┐
│ Deploy │
│ Prod │
└─────────┘
GitHub Actions Workflow
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ============================================
# Build and Test
# ============================================
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Run tests
run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Build
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build
path: dist/
# ============================================
# Security Scanning
# ============================================
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run dependency audit
run: npm audit --audit-level=high
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
ignore-unfixed: true
severity: 'CRITICAL,HIGH'
# ============================================
# Build Docker Image
# ============================================
docker:
needs: [build, security]
runs-on: ubuntu-latest
if: github.event_name == 'push'
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ============================================
# Deploy to Staging
# ============================================
deploy-staging:
needs: docker
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- name: Deploy to staging
run: |
echo "Deploying ${{ needs.docker.outputs.image-tag }} to staging"
# Add deployment commands here
# ============================================
# Deploy to Production
# ============================================
deploy-production:
needs: docker
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
run: |
echo "Deploying ${{ needs.docker.outputs.image-tag }} to production"
# Add deployment commands here
Reusable Workflow
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
image-tag:
required: true
type: string
secrets:
DEPLOY_TOKEN:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- name: Deploy
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
echo "Deploying ${{ inputs.image-tag }} to ${{ inputs.environment }}"
Docker Configuration
Multi-Stage Dockerfile (Node.js)
# ============================================
# Stage 1: Dependencies
# ============================================
FROM node:20-alpine AS deps
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install production dependencies
RUN npm ci --only=production
# ============================================
# Stage 2: Builder
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install all dependencies
RUN npm ci
# Copy source code
COPY . .
# Build application
RUN npm run build
# ============================================
# Stage 3: Runner
# ============================================
FROM node:20-alpine AS runner
WORKDIR /app
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser
# Set environment
ENV NODE_ENV=production
# Copy production dependencies
COPY /app/node_modules ./node_modules
# Copy built application
COPY /app/dist ./dist
COPY /app/package.json ./
# Change ownership
RUN chown -R appuser:nodejs /app
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# Start application
CMD ["node", "dist/index.js"]
Multi-Stage Dockerfile (Python)
# ============================================
# Stage 1: Builder
# ============================================
FROM python:3.12-slim AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy and install requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ============================================
# Stage 2: Runner
# ============================================
FROM python:3.12-slim AS runner
WORKDIR /app
# Create non-root user
RUN useradd --create-home --shell /bin/bash appuser
# Copy virtual environment
COPY /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy application code
COPY . .
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
# Start application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Docker Compose (Development)
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
target: builder # Use builder stage for dev
volumes:
- .:/app
- /app/node_modules # Preserve node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://postgres:postgres@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: npm run dev
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=myapp
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
Docker Compose (Production)
# docker-compose.prod.yml
version: '3.8'
services:
app:
image: ghcr.io/myorg/myapp:${IMAGE_TAG:-latest}
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Environment Management
Environment Configuration
# .env.example (commit this)
# Application
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# Redis
REDIS_URL=redis://localhost:6379
# Authentication
JWT_SECRET=your-secret-here
JWT_EXPIRES_IN=1h
# External Services
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
# Feature Flags
FEATURE_NEW_DASHBOARD=false
Environment Validation
// config/env.ts
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']),
PORT: z.string().transform(Number).default('3000'),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
JWT_EXPIRES_IN: z.string().default('1h'),
});
export type Env = z.infer<typeof envSchema>;
function validateEnv(): Env {
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('❌ Invalid environment variables:');
console.error(result.error.format());
process.exit(1);
}
return result.data;
}
export const env = validateEnv();
Secrets Management
GitHub Secrets Configuration
# Reference secrets in workflows
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
# Use environments for different secrets per stage
jobs:
deploy:
environment: production # Uses production secrets
HashiCorp Vault Integration
# .github/workflows/vault-deploy.yml
jobs:
deploy:
steps:
- name: Import Secrets
uses: hashicorp/vault-action@v2
with:
url: ${{ secrets.VAULT_URL }}
method: approle
roleId: ${{ secrets.VAULT_ROLE_ID }}
secretId: ${{ secrets.VAULT_SECRET_ID }}
secrets: |
secret/data/myapp DATABASE_URL | DATABASE_URL ;
secret/data/myapp API_KEY | API_KEY
- name: Deploy
env:
DATABASE_URL: ${{ env.DATABASE_URL }}
API_KEY: ${{ env.API_KEY }}
run: ./deploy.sh
Kubernetes Secrets
# k8s/secrets.yaml (use sealed-secrets or external-secrets in practice)
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
DATABASE_URL: "postgresql://..."
JWT_SECRET: "..."
---
# Reference in deployment
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
envFrom:
- secretRef:
name: app-secrets
Kubernetes Basics
Deployment Configuration
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: ghcr.io/myorg/myapp:latest
ports:
- containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
env:
- name: NODE_ENV
value: "production"
envFrom:
- secretRef:
name: app-secrets
- configMapRef:
name: app-config
---
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 3000
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
ConfigMap
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
FEATURE_FLAGS: |
{
"newDashboard": true,
"betaFeatures": false
}
Monitoring Setup
Health Check Endpoints
// routes/health.ts
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
app.get('/ready', async (req, res) => {
try {
// Check database connection
await db.query('SELECT 1');
// Check Redis connection
await redis.ping();
res.status(200).json({ status: 'ready' });
} catch (error) {
res.status(503).json({
status: 'not ready',
error: error.message
});
}
});
Prometheus Metrics
// metrics/prometheus.ts
import { Registry, Counter, Histogram } from 'prom-client';
const register = new Registry();
export const httpRequestsTotal = new Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'path', 'status'],
registers: [register],
});
export const httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'path'],
buckets: [0.1, 0.5, 1, 2, 5],
registers: [register],
});
// Middleware
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer({
method: req.method,
path: req.path
});
res.on('finish', () => {
httpRequestsTotal.inc({
method: req.method,
path: req.path,
status: res.statusCode
});
end();
});
next();
});
// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
Deployment Strategies
Blue-Green Deployment
┌─────────────────┐
│ Load Balancer │
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│ Blue │ │ Green │
│ (v1) │ │ (v2) │
│ LIVE │ │ IDLE │
└───────┘ └───────┘
After testing Green:
- Switch traffic to Green
- Blue becomes idle (rollback target)
Canary Deployment
┌─────────────────┐
│ Load Balancer │
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│Stable │ │Canary │
│ (v1) │ │ (v2) │
│ 90% │ │ 10% │
└───────┘ └───────┘
Gradually increase canary traffic:
10% → 25% → 50% → 100%
Rolling Update (Kubernetes)
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
Output Artifacts
When this skill is activated, I can help create:
- CI/CD Pipeline: GitHub Actions, GitLab CI, or other CI configs
- Dockerfile: Optimized multi-stage builds
- Docker Compose: Development and production configurations
- Kubernetes Manifests: Deployments, services, ingress
- Environment Configuration: Env files and validation
- Secrets Management: Vault, sealed-secrets setup
- Monitoring Setup: Health checks, metrics, logging
- Deployment Scripts: Automation scripts for deployments
Weekly Installs
2
Repository
navedanan/backg…-removerFirst Seen
Mar 3, 2026
Security Audits
Installed on
opencode2
claude-code2
github-copilot2
codex2
windsurf2
kimi-cli2