Use when implementing cicd functionality with production-grade patterns and safeguards.
Install to Claude Code
npx -y skills add 0xharryriddle/codex-field-kit --skill devops-cicd-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Devops Cicd Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/0xharryriddle-devops-cicd-expert)More formats (shields.io, HTML) on the badges page.
---
name: devops-cicd-expert
description: Use when implementing cicd functionality with production-grade patterns and safeguards.
metadata:
hermes:
tags: [codex-agent, devops-cloud-infra]
source: codex-field-kit/devops-cloud-infra
---
# Devops Cicd Expert
# Python DevOps/CI-CD Expert Agent
## Role & Expertise
I am a specialized Python DevOps and CI/CD expert with deep knowledge of:
**Core DevOps Areas:**
- **CI/CD Pipelines**: GitHub Actions, GitLab CI, Jenkins, Azure DevOps
- **Containerization**: Docker, Docker Compose, multi-stage builds
- **Orchestration**: Kubernetes, Helm charts, service meshes
- **Infrastructure as Code**: Terraform, Ansible, Pulumi with Python
- **Cloud Platforms**: AWS, GCP, Azure with Python SDKs
- **Monitoring & Logging**: Prometheus, Grafana, ELK stack, structured logging
- **Testing Automation**: Pytest, test pyramids, integration testing
- **Security**: Container security, secrets management, security scanning
**Python-Specific DevOps:**
- **Package Management**: Poetry, pip-tools, dependency management
- **Application Deployment**: WSGI/ASGI servers, blue-green deployments
- **Performance Monitoring**: APM tools, profiling, metrics collection
- **Configuration Management**: Environment-based configs, feature flags
- **Database Migrations**: Alembic, Django migrations in CI/CD
- **Microservices**: Service discovery, API gateways, distributed tracing
## Key Principles
### 1. **Automation First**
- Automate everything: builds, tests, deployments, monitoring
- Infrastructure as Code for reproducible environments
- Immutable infrastructure patterns
### 2. **Pipeline as Code**
- Version-controlled CI/CD configurations
- Reusable pipeline templates and components
- Environment parity and consistency
### 3. **Security by Design**
- Security scanning in pipelines
- Secrets management and rotation
- Least privilege access patterns
### 4. **Observability**
- Comprehensive logging, metrics, and tracing
- Proactive monitoring and alerting
- Performance optimization based on data
## Implementation Examples
### 1. **Complete CI/CD Pipeline with GitHub Actions**
**.github/workflows/python-app.yml**:
```yaml
name: Python Application CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
PYTHON_VERSION: "3.12"
POETRY_VERSION: "1.7.1"
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root
- name: Install project
run: poetry install --no-interaction
- name: Run pre-commit hooks
run: |
poetry run pre-commit install
poetry run pre-commit run --all-files
- name: Run type checking
run: poetry run mypy src/
- name: Run security scan
run: |
poetry run bandit -r src/
poetry run safety check
- name: Run tests with coverage
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
REDIS_URL: redis://localhost:6379/0
run: |
poetry run pytest \
--cov=src \
--cov-report=xml \
--cov-report=html \
--cov-fail-under=80 \
--junitxml=junit/test-results.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results-${{ matrix.python-version }}
path: |
junit/test-results.xml
htmlcov/
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
build:
needs: [test, security]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy to Kubernetes
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
IMAGE_TAG: ${{ github.sha }}
run: |
echo "$KUBE_CONFIG" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
# Update image tag in deployment
sed -i "s|IMAGE_TAG|$IMAGE_TAG|g" k8s/deployment.yaml
# Apply Kubernetes manifests
kubectl apply -f k8s/
# Wait for deployment to complete
kubectl rollout status deployment/myapp -n production --timeout=300s
```
### 2. **Multi-Stage Docker Configuration**
**Dockerfile**:
```dockerfile
# Multi-stage build for Python applications
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}-slim as base
# Set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install Poetry
ARG POETRY_VERSION=1.7.1
RUN pip install poetry==$POETRY_VERSION
# Configure Poetry
ENV POETRY_NO_INTERACTION=1 \
POETRY_VENV_IN_PROJECT=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache
# Development stage
FROM base as development
WORKDIR /app
COPY pyproject.toml poetry.lock ./
# Install development dependencies
RUN poetry install --with dev && rm -rf $POETRY_CACHE_DIR
COPY . .
EXPOSE 8000
CMD ["poetry", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
# Production build stage
FROM base as build
WORKDIR /app
COPY pyproject.toml poetry.lock ./
# Install only production dependencies
RUN poetry install --only=main && rm -rf $POETRY_CACHE_DIR
COPY . .
# Production stage
FROM python:${PYTHON_VERSION}-slim as production
# Security: create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Install runtime dependencies only
RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# Copy virtual environment from build stage
COPY --from=build /app/.venv /app/.venv
# Copy application code
COPY --from=build /app/src /app/src
COPY --from=build /app/pyproject.toml /app/
WORKDIR /app
# Switch to non-root user
USER appuser
# Add virtual environment to PATH
ENV PATH="/app/.venv/bin:$PATH"
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Use gunicorn for production
CMD ["gunicorn", "src.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
```
**docker-compose.yml** (for local development):
```yaml
version: '3.8'
services:
app:
build:
context: .
target: development
ports:
- "8000:8000"
volumes:
- .:/app
- /app/.venv # Anonymous volume for .venv
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379/0
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
command: ["poetry", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
volumes:
postgres_data:
```
### 3. **Kubernetes Deployment Configuration**
**k8s/namespace.yaml**:
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: myapp-production
labels:
name: myapp-production
```
**k8s/configmap.yaml**:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: myapp-production
data:
ENVIRONMENT: "production"
LOG_LEVEL: "INFO"
DATABASE_HOST: "postgres-service"
REDIS_HOST: "redis-service"
```
**k8s/secret.yaml**:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
namespace: myapp-production
type: Opaque
data:
# Base64 encoded values
DATABASE_PASSWORD: cGFzc3dvcmQ=
SECRET_KEY: c3VwZXItc2VjcmV0LWtleQ==
API_TOKEN: YXBpLXRva2VuLWhlcmU=
```
**k8s/deployment.yaml**:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp-production
labels:
app: myapp
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: ghcr.io/username/myapp:IMAGE_TAG
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: myapp-config
- secretRef:
name: myapp-secrets
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
```
**k8s/service.yaml**:
```yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-service
namespace: myapp-production
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: ClusterIP
```
**k8s/ingress.yaml**:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: myapp-production
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- api.myapp.com
secretName: myapp-tls
rules:
- host: api.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
```
### 4. **Infrastructure as Code with Terraform**
**terraform/main.tf**:
```hcl
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.20"
}
}
backend "s3" {
bucket = "myapp-terraform-state"
key = "infrastructure/terraform.tfstate"
region = "us-west-2"
}
}
provider "aws" {
region = var.aws_region
}
# EKS Cluster
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0"
cluster_name = var.cluster_name
cluster_version = "1.27"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
cluster_endpoint_public_access = true
eks_managed_node_groups = {
main = {
name = "main"
instance_types = ["m6i.large"]
min_size = 1
max_size = 10
desired_size = 3
pre_bootstrap_user_data = <<-EOT
#!/bin/bash
/etc/eks/bootstrap.sh ${var.cluster_name}
EOT
vpc_security_group_ids = [
aws_security_group.node_group_one.id
]
}
}
tags = {
Environment = var.environment
Terraform = "true"
}
}
# VPC
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.cluster_name}-vpc"
cidr = "10.0.0.0/16"
azs = slice(data.aws_availability_zones.available.names, 0, 3)
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
enable_nat_gateway = true
single_nat_gateway = false
enable_dns_hostnames = true
public_subnet_tags = {
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
"kubernetes.io/role/internal-elb" = "1"
}
}
# RDS Database
resource "aws_db_subnet_group" "education" {
name = "${var.cluster_name}-db"
subnet_ids = module.vpc.private_subnets
tags = {
Name = "${var.cluster_name} DB subnet group"
}
}
resource "aws_security_group" "rds" {
name_prefix = "${var.cluster_name}-rds-"
vpc_id = module.vpc.vpc_id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.cluster_name}-rds"
}
}
resource "aws_db_instance" "postgres" {
identifier = "${var.cluster_name}-postgres"
allocated_storage = 20
max_allocated_storage = 1000
storage_type = "gp3"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.micro"
db_name = var.database_name
username = var.database_username
password = var.database_password
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.education.name
backup_retention_period = 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
skip_final_snapshot = true
deletion_protection = false
performance_insights_enabled = true
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_enhanced_monitoring.arn
tags = {
Name = "${var.cluster_name}-postgres"
}
}
# ElastiCache Redis
resource "aws_elasticache_subnet_group" "redis" {
name = "${var.cluster_name}-redis"
subnet_ids = module.vpc.private_subnets
}
resource "aws_security_group" "redis" {
name_prefix = "${var.cluster_name}-redis-"
vpc_id = module.vpc.vpc_id
ingress {
from_port = 6379
to_port = 6379
protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr_block]
}
tags = {
Name = "${var.cluster_name}-redis"
}
}
resource "aws_elasticache_replication_group" "redis" {
replication_group_id = "${var.cluster_name}-redis"
description = "Redis cluster for ${var.cluster_name}"
node_type = "cache.t3.micro"
port = 6379
parameter_group_name = "default.redis7"
num_cache_clusters = 2
subnet_group_name = aws_elasticache_subnet_group.redis.name
security_group_ids = [aws_security_group.redis.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
tags = {
Name = "${var.cluster_name}-redis"
}
}
```
### 5. **Advanced Monitoring and Logging Setup**
**monitoring/prometheus.yaml**:
```yaml
# Prometheus configuration for Python applications
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "rules/*.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scrape_configs:
- job_name: 'myapp'
static_configs:
- targets: ['myapp-service:8000']
metrics_path: '/metrics'
scrape_interval: 5s
scrape_timeout: 5s
- job_name: 'postgres'
static_configs:
- targets: ['postgres-exporter:9187']
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
```
**Python application metrics integration**:
```python
# src/monitoring.py
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from functools import wraps
import time
from typing import Callable, Any
import logging
# Metrics definitions
REQUEST_COUNT = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status_code']
)
REQUEST_DURATION = Histogram(
'http_request_duration_seconds',
'HTTP request duration in seconds',
['method', 'endpoint']
)
ACTIVE_CONNECTIONS = Gauge(
'active_connections',
'Number of active connections'
)
DATABASE_POOL_SIZE = Gauge(
'database_pool_size',
'Current database connection pool size'
)
CELERY_TASK_DURATION = Histogram(
'celery_task_duration_seconds',
'Time spent on Celery tasks',
['task_name', 'status']
)
def track_request_metrics(func: Callable) -> Callable:
Scanned 9/11/2026
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!