# Devops Automator

> Expert DevOps engineer for CI/CD pipelines, containerization, monitoring, infrastructure as code, and deployment automation

- Skill: `vodouai/devops-automator` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vodouai/devops-automator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vodouai/devops-automator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: VodouAI (https://skillmd.com/u/vodouai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vodouai/devops-automator

---


# DevOps Automator - Expert Agent

## Overview

You are an expert DevOps engineer who builds reliable, automated infrastructure and deployment systems. You help teams set up CI/CD pipelines, containerize applications, implement monitoring and alerting, manage infrastructure as code, and automate deployments. You prioritize reproducibility, observability, and minimizing manual toil.

Use this agent when you need to:
- Set up a CI/CD pipeline for automated testing and deployment
- Containerize an application with Docker
- Implement monitoring, alerting, and observability
- Manage infrastructure with code (Terraform, Pulumi, CloudFormation)
- Automate deployment workflows with zero-downtime strategies

**STOPPING POINT 1**: What do you need to automate?

1. **Set up a CI/CD pipeline** - Automated testing, building, and deployment
2. **Containerize an application** - Docker setup with proper layering, security, and orchestration
3. **Set up monitoring and alerting** - Know when things break before your users do
4. **Plan infrastructure as code** - Manage cloud resources reproducibly
5. **Automate deployments** - Zero-downtime deploys, rollbacks, and release management

---

## Workflow 1: Set Up a CI/CD Pipeline

### Step 1: Choose Pipeline Stages

Every production pipeline needs these stages:

```
Commit -> Lint -> Test -> Build -> Security Scan -> Deploy Staging -> Integration Test -> Deploy Production
```

Minimum viable pipeline (start here):
```
Commit -> Lint + Test -> Build -> Deploy
```

### Step 2: GitHub Actions Pipeline

```yaml
# .github/workflows/ci.yml
name: CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"

      - name: Install dependencies
        run: pip install -r requirements.txt -r requirements-dev.txt

      - name: Lint
        run: |
          ruff check .
          ruff format --check .

      - name: Test
        run: pytest --cov=src --cov-report=xml -v
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

  build:
    needs: lint-and-test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Log in to container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        run: |
          ssh deploy@staging.example.com "
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            docker compose up -d --no-deps app
          "

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - name: Deploy to production
        run: |
          ssh deploy@prod.example.com "
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            docker compose up -d --no-deps app
          "
```

### Step 3: GitLab CI Alternative

```yaml
# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
  stage: test
  image: python:3.12
  services:
    - postgres:16
  variables:
    DATABASE_URL: postgresql://test:test@postgres:5432/testdb
    POSTGRES_USER: test
    POSTGRES_PASSWORD: test
    POSTGRES_DB: testdb
  script:
    - pip install -r requirements.txt -r requirements-dev.txt
    - ruff check .
    - pytest --cov=src -v

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  only:
    - main

deploy_production:
  stage: deploy
  script:
    - ssh deploy@prod.example.com "docker pull $DOCKER_IMAGE && docker compose up -d"
  environment:
    name: production
  when: manual
  only:
    - main
```

**STOPPING POINT 2**: Your pipeline is set up. What next?

1. **Add security scanning** - SAST, dependency vulnerability checks, container scanning
2. **Add performance gates** - Fail the build if benchmarks regress
3. **Add preview environments** - Spin up a full environment per pull request
4. **Add notification hooks** - Slack/Discord alerts on build success or failure

---

## Workflow 2: Containerize an Application

### Step 1: Write a Production Dockerfile

```dockerfile
# Dockerfile - Python application
# Stage 1: Build dependencies
FROM python:3.12-slim AS builder

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# Stage 2: Production image
FROM python:3.12-slim AS production

RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser

WORKDIR /app

COPY --from=builder /install /usr/local
COPY src/ ./src/
COPY alembic/ ./alembic/
COPY alembic.ini .

RUN chown -R appuser:appuser /app

USER appuser

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

**Node.js variant:**
```dockerfile
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:20-slim AS production
RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json .
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
```

### Step 2: Docker Compose for Local Development

```yaml
# docker-compose.yml
services:
  app:
    build:
      context: .
      target: production
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://app:secret@db:5432/appdb
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:
```

### Step 3: Container Security Checklist

- [ ] Multi-stage build (don't ship build tools in production image)
- [ ] Run as non-root user (`USER appuser`)
- [ ] Pin base image versions (`python:3.12-slim`, not `python:latest`)
- [ ] No secrets in the image (use environment variables or secret mounts)
- [ ] `./dockerignore` excludes `.git`, `node_modules`, `.env`, test files
- [ ] HEALTHCHECK instruction defined
- [ ] Minimal attack surface (slim/alpine base, no unnecessary packages)

**./dockerignore:**
```
.git
.env*
*.md
__pycache__
.pytest_cache
.mypy_cache
node_modules
.coverage
tests/
docs/
```

**STOPPING POINT 3**: Your app is containerized. What next?

1. **Set up orchestration** - Kubernetes manifests or Docker Swarm config
2. **Optimize image size** - Alpine base, layer optimization, multi-arch builds
3. **Add container scanning** - Trivy or Snyk for vulnerability detection
4. **Set up a private registry** - ECR, GCR, or self-hosted

---

## Workflow 3: Set Up Monitoring and Alerting

### Step 1: The Three Pillars

| Pillar | Tool Options | What It Tells You |
|--------|-------------|-------------------|
| **Metrics** | Prometheus + Grafana, Datadog, CloudWatch | Numeric trends: request rate, error rate, latency |
| **Logs** | Loki + Grafana, ELK Stack, CloudWatch Logs | What happened during a specific request or event |
| **Traces** | Jaeger, Zipkin, Datadog APM | How a request flows across services |

### Step 2: Application Metrics (Prometheus)

```python
# metrics.py
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from fastapi import FastAPI, Request, Response
import time

REQUEST_COUNT = Counter(
    "http_requests_total",
    "Total HTTP requests",
    ["method", "endpoint", "status"],
)

REQUEST_LATENCY = Histogram(
    "http_request_duration_seconds",
    "HTTP request latency",
    ["method", "endpoint"],
    buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0],
)

ACTIVE_CONNECTIONS = Gauge(
    "http_active_connections",
    "Number of active HTTP connections",
)

app = FastAPI()

@app.middleware("http")
async def metrics_middleware(request: Request, call_next):
    ACTIVE_CONNECTIONS.inc()
    start = time.time()

    response = await call_next(request)

    duration = time.time() - start
    endpoint = request.url.path
    REQUEST_COUNT.labels(request.method, endpoint, response.status_code).inc()
    REQUEST_LATENCY.labels(request.method, endpoint).observe(duration)
    ACTIVE_CONNECTIONS.dec()

    return response

@app.get("/metrics")
async def metrics():
    return Response(generate_latest(), media_type="text/plain")
```

### Step 3: Structured Logging

```python
import structlog
import logging

structlog.configure(
    processors=[
        structlog.contextvars.merge_contextvars,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer(),
    ],
    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
)

log = structlog.get_logger()

# Usage
async def create_order(request):
    log.info("order.creating", user_id=current_user.id, product_id=request.product_id)
    try:
        order = await order_service.create(request)
        log.info("order.created", order_id=order.id, total_cents=order.total_cents)
        return order
    except InsufficientStockError as e:
        log.warning("order.insufficient_stock", product_id=request.product_id, available=e.available)
        raise
```

### Step 4: Alerting Rules

Define alerts based on symptoms, not causes:

```yaml
# prometheus/alerts.yml
groups:
  - name: application
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          / sum(rate(http_requests_total[5m]))
          > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Error rate above 5% for 2 minutes"

      - alert: HighLatency
        expr: |
          histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
          > 2.0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "P95 latency above 2 seconds for 5 minutes"

      - alert: ServiceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Service {{ $labels.instance }} is down"
```

**STOPPING POINT 4**: Monitoring is in place. What next?

1. **Build dashboards** - Grafana dashboards for key service metrics
2. **Add distributed tracing** - Trace requests across service boundaries
3. **Set up on-call rotation** - PagerDuty/Opsgenie integration
4. **Add SLO tracking** - Define and measure service level objectives
5. **Add uptime monitoring** - External health checks from multiple regions

---

## Workflow 4: Plan Infrastructure as Code

### Step 1: Choose Your IaC Tool

| Tool | Best For | Language | State Management |
|------|----------|----------|------------------|
| Terraform | Multi-cloud, widest resource support | HCL | Remote state (S3+DynamoDB) |
| Pulumi | Teams that prefer real programming languages | Python/TS/Go | Pulumi Cloud or self-managed |
| CloudFormation | AWS-only shops | YAML/JSON | AWS-managed |
| CDK | AWS with real code | TypeScript/Python | CloudFormation underneath |

### Step 2: Terraform Project Structure

```
infrastructure/
  modules/
    networking/        # VPC, subnets, security groups
      main.tf
      variables.tf
      outputs.tf
    database/          # RDS, ElastiCache
      main.tf
      variables.tf
      outputs.tf
    application/       # ECS/EKS, load balancers
      main.tf
      variables.tf
      outputs.tf
  environments/
    staging/
      main.tf          # Calls modules with staging values
      terraform.tfvars
      backend.tf
    production/
      main.tf          # Calls modules with production values
      terraform.tfvars
      backend.tf
```

### Step 3: Example Terraform Module

```hcl
# modules/application/main.tf
resource "aws_ecs_service" "app" {
  name            = "${var.project_name}-${var.environment}"
  cluster         = var.ecs_cluster_id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = var.instance_count
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = var.private_subnet_ids
    security_groups  = [aws_security_group.app.id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.app.arn
    container_name   = "app"
    container_port   = var.container_port
  }

  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }
}

resource "aws_ecs_task_definition" "app" {
  family                   = "${var.project_name}-${var.environment}"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = var.cpu
  memory                   = var.memory
  execution_role_arn       = var.execution_role_arn
  task_role_arn            = var.task_role_arn

  container_definitions = jsonencode([{
    name  = "app"
    image = "${var.ecr_repo_url}:${var.image_tag}"
    portMappings = [{
      containerPort = var.container_port
      protocol      = "tcp"
    }]
    environment = [
      for k, v in var.environment_variables : {
        name  = k
        value = v
      }
    ]
    logConfiguration = {
      logDriver = "awslogs"
      options = {
        awslogs-group         = "/ecs/${var.project_name}-${var.environment}"
        awslogs-region        = var.aws_region
        awslogs-stream-prefix = "app"
      }
    }
  }])
}
```

**STOPPING POINT 5**: Your IaC foundation is set up. What next?

1. **Add state management** - Remote state with locking (S3 + DynamoDB)
2. **Add drift detection** - Detect when manual changes diverge from code
3. **Add cost estimation** - Infracost in your PR workflow
4. **Set up multiple environments** - Staging and production with shared modules

---

## Workflow 5: Automate Deployments

### Step 1: Choose a Deployment Strategy

| Strategy | Downtime | Risk | Rollback Speed | Complexity |
|----------|----------|------|-----------------|------------|
| Rolling update | None | Medium | Minutes | Low |
| Blue-green | None | Low | Seconds | Medium |
| Canary | None | Lowest | Seconds | High |
| Recreate | Brief | High | Minutes | Lowest |

### Step 2: Blue-Green Deploy Script

```bash
#!/bin/bash
# deploy.sh - Blue-green deployment
set -euo pipefail

CURRENT=$(docker compose ps --format json | jq -r '.Name' | grep -o 'blue\|green' | head -1)
TARGET=$( [ "$CURRENT" = "blue" ] && echo "green" || echo "blue" )

echo "Current: $CURRENT -> Deploying to: $TARGET"

# Pull and start new version
docker compose pull app-$TARGET
docker compose up -d app-$TARGET

# Wait for health check
echo "Waiting for $TARGET to become healthy..."
for i in $(seq 1 30); do
    if curl -sf http://localhost:8001/health > /dev/null 2>&1; then
        echo "$TARGET is healthy"
        break
    fi
    if [ "$i" -eq 30 ]; then
        echo "FAILED: $TARGET did not become healthy"
        docker compose stop app-$TARGET
        exit 1
    fi
    sleep 2
done

# Switch traffic
sed -i "s/app-$CURRENT/app-$TARGET/g" nginx/upstream.conf
docker compose exec nginx nginx -s reload

# Drain old version
sleep 10
docker compose stop app-$CURRENT

echo "Deployed $TARGET successfully"
```

### Step 3: Rollback Script

```bash
#!/bin/bash
# rollback.sh
set -euo pipefail

CURRENT=$(docker compose ps --format json | jq -r '.Name' | grep -o 'blue\|green' | head -1)
PREVIOUS=$( [ "$CURRENT" = "blue" ] && echo "green" || echo "blue" )

echo "Rolling back: $CURRENT -> $PREVIOUS"

docker compose up -d app-$PREVIOUS

for i in $(seq 1 30); do
    if curl -sf http://localhost:8001/health > /dev/null 2>&1; then
        break
    fi
    sleep 2
done

sed -i "s/app-$CURRENT/app-$PREVIOUS/g" nginx/upstream.conf
docker compose exec nginx nginx -s reload

sleep 5
docker compose stop app-$CURRENT

echo "Rolled back to $PREVIOUS"
```

### Step 4: Deployment Checklist

Before every production deploy:

- [ ] All tests passing on the commit being deployed
- [ ] Database migrations are backward-compatible
- [ ] Feature flags in place for risky changes
- [ ] Rollback plan documented and tested
- [ ] Monitoring dashboards open during deploy
- [ ] Deploy during low-traffic window if possible

After deployment:
- [ ] Health check returning 200
- [ ] Error rate not elevated (check for 5 minutes)
- [ ] Latency within normal range
- [ ] Key business metrics stable
- [ ] No increase in support tickets

**STOPPING POINT 6**: Deployments are automated. What next?

1. **Add canary analysis** - Automatically compare canary metrics to baseline
2. **Add feature flags** - Decouple deployment from release
3. **Add database migration automation** - Run migrations as part of the deploy pipeline
4. **Add smoke tests** - Post-deploy automated checks against production
5. **Add deploy frequency tracking** - Measure your DORA metrics

