ring/platforms/opencode/standards/devops.md
Gandalf f85aa61440
feat: add OpenCode platform integration (consolidate ring-for-opencode)
Move OpenCode runtime plugin and installer into Ring monorepo under platforms/opencode/.
The installer reads skills, agents, and commands directly from the Ring monorepo's
canonical directories (default/, dev-team/, pm-team/, etc.) — zero asset duplication.

What's included:
- installer.sh: reads from Ring dirs, applies frontmatter/tool transforms, installs to ~/.config/opencode/
- plugin/: TypeScript runtime (RingUnifiedPlugin) with hooks, lifecycle, loaders
- src/: CLI (doctor, config-manager)
- prompts/: session-start and context-injection templates
- standards/: coding standards (from dev-team/docs/)
- ring.jsonc: default config with full 86-skill/35-agent/33-command inventory

What's NOT included (intentionally):
- assets/ directory: eliminated, content comes from Ring monorepo
- scripts/codereview/: eliminated, replaced by mithril
- using-ring-opencode skill: uses canonical using-ring instead

Transforms applied by installer:
- Agent: type→mode, strip version/changelog/output_schema/input_schema
- Skill: keep name+description frontmatter, body unchanged
- Command: strip argument-hint (unsupported by OpenCode)
- All: normalize tool names (Bash→bash, Read→read, etc.)
- All: strip Model Requirement sections from agents

Replaces: LerianStudio/ring-for-opencode repository
Generated-by: Gandalf
AI-Model: claude-opus-4
2026-03-07 22:46:47 -03:00

27 KiB

DevOps Standards

⚠️ MAINTENANCE: This file is indexed in dev-team/skills/shared-patterns/standards-coverage-table.md. When adding/removing ## sections, follow FOUR-FILE UPDATE RULE in CLAUDE.md: (1) edit standards file, (2) update TOC, (3) update standards-coverage-table.md, (4) update agent file.

This file defines the specific standards for DevOps, SRE, and infrastructure.

Reference: Always consult docs/PROJECT_RULES.md for common project standards.


Table of Contents

# Section Description
1 Cloud Provider AWS, GCP, Azure services
2 Infrastructure as Code Terraform patterns and best practices
3 Containers Dockerfile, Docker Compose, .env
4 Helm Chart structure and configuration
5 Observability Logging and tracing standards
6 Security Secrets management, network policies
7 Makefile Standards Required commands and patterns
8 CI/CD Pipeline GitHub Actions, required stages, branch protection

Meta-sections (not checked by agents):

  • Checklist - Self-verification before deploying

Cloud Provider

Provider Primary Services
AWS EKS, RDS, S3, Lambda, SQS
GCP GKE, Cloud SQL, Cloud Storage
Azure AKS, Azure SQL, Blob Storage

Infrastructure as Code

Terraform (Preferred)

Project Structure

/terraform
  /modules
    /vpc
      main.tf
      variables.tf
      outputs.tf
    /eks
    /rds
  /environments
    /dev
      main.tf
      terraform.tfvars
    /staging
    /prod
  backend.tf
  providers.tf
  versions.tf

State Management

# backend.tf
terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "env/prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

Module Pattern

# modules/eks/main.tf
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 19.0"

  cluster_name    = var.cluster_name
  cluster_version = var.kubernetes_version

  vpc_id     = var.vpc_id
  subnet_ids = var.subnet_ids

  eks_managed_node_groups = {
    default = {
      min_size     = var.min_nodes
      max_size     = var.max_nodes
      desired_size = var.desired_nodes

      instance_types = var.instance_types
      capacity_type  = "ON_DEMAND"
    }
  }

  tags = var.tags
}

# modules/eks/variables.tf
variable "cluster_name" {
  type        = string
  description = "Name of the EKS cluster"
}

variable "kubernetes_version" {
  type        = string
  default     = "1.28"
  description = "Kubernetes version"
}

# modules/eks/outputs.tf
output "cluster_endpoint" {
  value = module.eks.cluster_endpoint
}

output "cluster_name" {
  value = module.eks.cluster_name
}

Best Practices

# Always use version constraints
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Use data sources for existing resources
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

# Use locals for computed values
locals {
  account_id = data.aws_caller_identity.current.account_id
  region     = data.aws_region.current.name
  name_prefix = "${var.project}-${var.environment}"
}

# Always tag resources
resource "aws_instance" "example" {
  # ...

  tags = merge(var.common_tags, {
    Name        = "${local.name_prefix}-instance"
    Environment = var.environment
    ManagedBy   = "terraform"
  })
}

Containers

Dockerfile Best Practices

# Multi-stage build for minimal images
FROM golang:1.22-alpine AS builder

WORKDIR /app

# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download

# Build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/api

# Production image
FROM gcr.io/distroless/static-debian12:nonroot

COPY --from=builder /app/server /server

USER nonroot:nonroot

EXPOSE 8080

ENTRYPOINT ["/server"]

Image Guidelines

Guideline Reason
Use multi-stage builds Smaller images
Use distroless/alpine Minimal attack surface
Run as non-root Security
Pin versions Reproducibility
Use .dockerignore Smaller context

Docker Compose (Local Dev)

MANDATORY: Use .env file for environment variables instead of inline definitions.

# docker-compose.yml
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    image: postgres:15-alpine
    env_file:
      - .env
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

.env File Structure

# .env (add to .gitignore)

# Application
ENV_NAME=local
LOG_LEVEL=debug
SERVER_ADDRESS=:8080

# PostgreSQL
POSTGRES_USER=user
POSTGRES_PASSWORD=pass
POSTGRES_DB=app
DB_HOST=db
DB_PORT=5432

# Redis
REDIS_HOST=redis
REDIS_PORT=6379

# Telemetry
ENABLE_TELEMETRY=false
Guideline Reason
Use env_file directive Centralized configuration
Add .env to .gitignore Prevent secrets in version control
Provide .env.example Document required variables
Use consistent naming Match application config struct

Helm

Chart Structure

/charts/api
  Chart.yaml
  values.yaml
  values-dev.yaml
  values-prod.yaml
  /templates
    deployment.yaml
    service.yaml
    ingress.yaml
    configmap.yaml
    secret.yaml
    hpa.yaml
    _helpers.tpl

Chart.yaml

apiVersion: v2
name: api
description: API service Helm chart
type: application
version: 1.0.0
appVersion: "1.0.0"
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled

values.yaml

# values.yaml
replicaCount: 3

image:
  repository: company/api
  tag: latest
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: api.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: api-tls
      hosts:
        - api.example.com

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

postgresql:
  enabled: false # Use external database

Observability

Logging (Structured JSON)

{
  "timestamp": "2024-01-15T10:30:00Z",
  "level": "info",
  "message": "Request completed",
  "request_id": "abc-123",
  "user_id": "usr_456",
  "method": "POST",
  "path": "/api/v1/users",
  "status": 201,
  "duration_ms": 45,
  "trace_id": "def-789"
}

Tracing (OpenTelemetry)

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024

exporters:
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [jaeger]

Security

Secrets Management

# Use External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: api-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: api-secrets
  data:
    - secretKey: database-url
      remoteRef:
        key: prod/api/database
        property: url

Network Policies

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-network-policy
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              name: database
      ports:
        - protocol: TCP
          port: 5432

Makefile Standards

All projects MUST include a Makefile with standardized commands for consistent developer experience.

Required Commands

Command Purpose Category
make build Build all components Core
make lint Run linters (golangci-lint) Code Quality
make test Run all tests Testing
make cover Generate test coverage report Testing
make test-unit Run unit tests only Testing
make up Start all services with Docker Compose Docker
make down Stop all services Docker
make start Start existing containers Docker
make stop Stop running containers Docker
make restart Restart all containers Docker
make rebuild-up Rebuild and restart services Docker
make set-env Copy .env.example to .env Setup
make dev-setup Install development tools (swag, golangci-lint, etc.) Setup
make generate-docs Generate API documentation (Swagger) Documentation
make migrate-up Apply all pending database migrations Database
make migrate-down Rollback last migration Database
make migrate-create Create new migration file Database
make migrate-version Show current migration version Database

Component Delegation Pattern (Monorepo)

For monorepo projects with multiple components:

Command Purpose
make infra COMMAND=<cmd> Run command in infra component
make onboarding COMMAND=<cmd> Run command in onboarding component
make all-components COMMAND=<cmd> Run command across all components

Root Makefile Example

# Project Root Makefile

# Component directories
INFRA_DIR := ./components/infra
ONBOARDING_DIR := ./components/onboarding
TRANSACTION_DIR := ./components/transaction

COMPONENTS := $(INFRA_DIR) $(ONBOARDING_DIR) $(TRANSACTION_DIR)

# Docker command detection
DOCKER_CMD := $(shell if docker compose version >/dev/null 2>&1; then echo "docker compose"; else echo "docker-compose"; fi)

#-------------------------------------------------------
# Core Commands
#-------------------------------------------------------

.PHONY: build
build:
	@for dir in $(COMPONENTS); do \
		echo "Building in $$dir..."; \
		(cd $$dir && $(MAKE) build) || exit 1; \
	done
	@echo "[ok] All components built successfully"

.PHONY: test
test:
	@for dir in $(COMPONENTS); do \
		(cd $$dir && $(MAKE) test) || exit 1; \
	done

.PHONY: test-unit
test-unit:
	@for dir in $(COMPONENTS); do \
		(cd $$dir && go test -v -short ./...) || exit 1; \
	done

.PHONY: cover
cover:
	@sh ./scripts/coverage.sh
	@go tool cover -html=coverage.out -o coverage.html
	@echo "Coverage report generated at coverage.html"

#-------------------------------------------------------
# Code Quality Commands
#-------------------------------------------------------

.PHONY: lint
lint:
	@for dir in $(COMPONENTS); do \
		if find "$$dir" -name "*.go" -type f | grep -q .; then \
			(cd $$dir && golangci-lint run --fix ./...) || exit 1; \
		fi; \
	done
	@echo "[ok] Linting completed successfully"

#-------------------------------------------------------
# Docker Commands
#-------------------------------------------------------

.PHONY: up
up:
	@for dir in $(COMPONENTS); do \
		if [ -f "$$dir/docker-compose.yml" ]; then \
			(cd $$dir && $(DOCKER_CMD) -f docker-compose.yml up -d) || exit 1; \
		fi; \
	done
	@echo "[ok] All services started successfully"

.PHONY: down
down:
	@for dir in $(COMPONENTS); do \
		if [ -f "$$dir/docker-compose.yml" ]; then \
			(cd $$dir && $(DOCKER_CMD) -f docker-compose.yml down) || exit 1; \
		fi; \
	done

.PHONY: start
start:
	@for dir in $(COMPONENTS); do \
		if [ -f "$$dir/docker-compose.yml" ]; then \
			(cd $$dir && $(DOCKER_CMD) -f docker-compose.yml start) || exit 1; \
		fi; \
	done

.PHONY: stop
stop:
	@for dir in $(COMPONENTS); do \
		if [ -f "$$dir/docker-compose.yml" ]; then \
			(cd $$dir && $(DOCKER_CMD) -f docker-compose.yml stop) || exit 1; \
		fi; \
	done

.PHONY: restart
restart:
	@make stop && make start

.PHONY: rebuild-up
rebuild-up:
	@for dir in $(COMPONENTS); do \
		if [ -f "$$dir/docker-compose.yml" ]; then \
			(cd $$dir && $(DOCKER_CMD) -f docker-compose.yml down && \
			 $(DOCKER_CMD) -f docker-compose.yml build && \
			 $(DOCKER_CMD) -f docker-compose.yml up -d) || exit 1; \
		fi; \
	done

#-------------------------------------------------------
# Setup Commands
#-------------------------------------------------------

.PHONY: set-env
set-env:
	@for dir in $(COMPONENTS); do \
		if [ -f "$$dir/.env.example" ] && [ ! -f "$$dir/.env" ]; then \
			cp "$$dir/.env.example" "$$dir/.env"; \
			echo "Created .env in $$dir"; \
		fi; \
	done

#-------------------------------------------------------
# Documentation Commands
#-------------------------------------------------------

.PHONY: generate-docs
generate-docs:
	@./scripts/generate-docs.sh

#-------------------------------------------------------
# Component Delegation
#-------------------------------------------------------

.PHONY: infra
infra:
	@if [ -z "$(COMMAND)" ]; then \
		echo "Error: Use COMMAND=<cmd>"; exit 1; \
	fi
	@cd $(INFRA_DIR) && $(MAKE) $(COMMAND)

.PHONY: onboarding
onboarding:
	@if [ -z "$(COMMAND)" ]; then \
		echo "Error: Use COMMAND=<cmd>"; exit 1; \
	fi
	@cd $(ONBOARDING_DIR) && $(MAKE) $(COMMAND)

.PHONY: all-components
all-components:
	@if [ -z "$(COMMAND)" ]; then \
		echo "Error: Use COMMAND=<cmd>"; exit 1; \
	fi
	@for dir in $(COMPONENTS); do \
		(cd $$dir && $(MAKE) $(COMMAND)) || exit 1; \
	done

Component Makefile Example

# Component Makefile (e.g., components/onboarding/Makefile)

SERVICE_NAME := onboarding-service
ARTIFACTS_DIR := ./artifacts

.PHONY: build test lint up down

build:
	@go build -o $(ARTIFACTS_DIR)/$(SERVICE_NAME) ./cmd/app

test:
	@go test -v ./...

lint:
	@golangci-lint run --fix ./...

up:
	@docker compose -f docker-compose.yml up -d

down:
	@docker compose -f docker-compose.yml down

Database Migration Commands (MANDATORY)

MUST: All projects with a database include these migration commands using golang-migrate:

#-------------------------------------------------------
# Database Migration Commands
#-------------------------------------------------------

# Database URL from environment or default
DATABASE_URL ?= postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=$(DB_SSLMODE)
MIGRATE = migrate -path ./migrations -database "$(DATABASE_URL)"

.PHONY: migrate-up
migrate-up: ## Apply all pending migrations
	@echo "Applying migrations..."
	$(MIGRATE) up
	@echo "[ok] Migrations applied successfully"

.PHONY: migrate-down
migrate-down: ## Rollback last migration
	@echo "Rolling back last migration..."
	$(MIGRATE) down 1
	@echo "[ok] Rollback completed"

.PHONY: migrate-down-all
migrate-down-all: ## Rollback all migrations (DANGEROUS)
	@echo "WARNING: Rolling back ALL migrations..."
	@read -p "Are you sure? [y/N] " confirm && [ "$$confirm" = "y" ]
	$(MIGRATE) down
	@echo "[ok] All migrations rolled back"

.PHONY: migrate-create
migrate-create: ## Create new migration (usage: make migrate-create NAME=create_users)
	@if [ -z "$(NAME)" ]; then \
		echo "Error: NAME is required. Usage: make migrate-create NAME=create_users"; \
		exit 1; \
	fi
	migrate create -ext sql -dir ./migrations -seq $(NAME)
	@echo "[ok] Migration files created in ./migrations/"

.PHONY: migrate-version
migrate-version: ## Show current migration version
	$(MIGRATE) version

.PHONY: migrate-force
migrate-force: ## Force set migration version (usage: make migrate-force VERSION=1)
	@if [ -z "$(VERSION)" ]; then \
		echo "Error: VERSION is required. Usage: make migrate-force VERSION=1"; \
		exit 1; \
	fi
	$(MIGRATE) force $(VERSION)
	@echo "[ok] Version forced to $(VERSION)"

.PHONY: migrate-status
migrate-status: ## Show migration status
	@echo "Current migration version:"
	@$(MIGRATE) version 2>/dev/null || echo "No migrations applied yet"

Usage examples:

# Apply all pending migrations
make migrate-up

# Rollback last migration (one feature = one rollback)
make migrate-down

# Create new migration for a feature
make migrate-create NAME=add_user_preferences

# Check current version
make migrate-version

# Force version after manual fix (use with caution)
make migrate-force VERSION=5

Documentation Commands (MANDATORY)

All projects with API endpoints MUST include Swagger generation using swaggo:

#-------------------------------------------------------
# Documentation Commands
#-------------------------------------------------------

.PHONY: generate-docs
generate-docs: ## Generate Swagger API documentation
	@echo "Generating Swagger documentation..."
	@if ! command -v swag >/dev/null 2>&1; then \
		echo "Error: swag is not installed. Run: make dev-setup"; \
		exit 1; \
	fi
	swag init -g cmd/app/main.go -o api --parseDependency --parseInternal
	@echo "[ok] Swagger documentation generated in api/"

.PHONY: serve-docs
serve-docs: ## Serve Swagger UI locally (requires swagger-ui)
	@echo "Serving Swagger UI at http://localhost:8081"
	@docker run -p 8081:8080 -e SWAGGER_JSON=/api/swagger.json -v $(PWD)/api:/api swaggerapi/swagger-ui

Command parameters:

Flag Purpose
-g cmd/app/main.go Entry point file with API metadata annotations
-o api Output directory for generated files
--parseDependency Parse external dependencies for models
--parseInternal Parse internal packages for types

Generated files:

/api
  docs.go         # Go code for embedding (GENERATED - do not edit)
  swagger.json    # OpenAPI spec in JSON (GENERATED - do not edit)
  swagger.yaml    # OpenAPI spec in YAML (GENERATED - do not edit)

FORBIDDEN: Editing generated files directly. Always edit the annotations in source code.

Development Setup Commands (MANDATORY)

All projects MUST include a dev-setup command to install required tools:

#-------------------------------------------------------
# Development Setup Commands
#-------------------------------------------------------

.PHONY: dev-setup
dev-setup: ## Install development tools
	@echo "Installing development tools..."

	@# golangci-lint
	@if ! command -v golangci-lint >/dev/null 2>&1; then \
		echo "Installing golangci-lint..."; \
		go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \
	else \
		echo "[ok] golangci-lint already installed"; \
	fi

	@# swag (Swagger generator)
	@if ! command -v swag >/dev/null 2>&1; then \
		echo "Installing swag..."; \
		go install github.com/swaggo/swag/cmd/swag@latest; \
	else \
		echo "[ok] swag already installed"; \
	fi

	@# golang-migrate
	@if ! command -v migrate >/dev/null 2>&1; then \
		echo "Installing golang-migrate..."; \
		go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest; \
	else \
		echo "[ok] migrate already installed"; \
	fi

	@# mockgen (for GoMock)
	@if ! command -v mockgen >/dev/null 2>&1; then \
		echo "Installing mockgen..."; \
		go install go.uber.org/mock/mockgen@latest; \
	else \
		echo "[ok] mockgen already installed"; \
	fi

	@echo "[ok] All development tools installed"

.PHONY: check-tools
check-tools: ## Verify all required tools are installed
	@echo "Checking required tools..."
	@command -v go >/dev/null 2>&1 || { echo "❌ go not found"; exit 1; }
	@command -v golangci-lint >/dev/null 2>&1 || { echo "❌ golangci-lint not found"; exit 1; }
	@command -v swag >/dev/null 2>&1 || { echo "❌ swag not found"; exit 1; }
	@command -v migrate >/dev/null 2>&1 || { echo "❌ migrate not found"; exit 1; }
	@command -v mockgen >/dev/null 2>&1 || { echo "❌ mockgen not found"; exit 1; }
	@command -v docker >/dev/null 2>&1 || { echo "❌ docker not found"; exit 1; }
	@echo "[ok] All required tools are installed"

Required tools:

Tool Purpose Installation
golangci-lint Code linting go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
swag Swagger generation go install github.com/swaggo/swag/cmd/swag@latest
migrate Database migrations go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
mockgen Mock generation go install go.uber.org/mock/mockgen@latest

Generate Mocks Command (MANDATORY)

#-------------------------------------------------------
# Code Generation Commands
#-------------------------------------------------------

.PHONY: generate
generate: ## Run all code generation (mocks, etc.)
	@echo "Running go generate..."
	@go generate ./...
	@echo "[ok] Code generation completed"

.PHONY: generate-mocks
generate-mocks: ## Generate mock files using mockgen
	@echo "Generating mocks..."
	@go generate ./...
	@echo "[ok] Mocks generated"

CI/CD Pipeline (MANDATORY)

This section covers CI/CD pipeline patterns and automation requirements.

CI Pipeline Stages (MANDATORY)

All services MUST have CI pipelines with these stages:

# .github/workflows/ci.yml
name: CI

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: "1.24"
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v4

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: "1.24"
      - name: Run tests
        run: make test
      - name: Check coverage
        run: make cover

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run govulncheck
        run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...

  build:
    runs-on: ubuntu-latest
    needs: [lint, test, security]
    steps:
      - uses: actions/checkout@v4
      - name: Build Docker image
        run: docker build -t ${{ github.repository }}:${{ github.sha }} .

Required CI Stages

Stage Purpose Failure Action
lint Code quality Block merge
test Unit tests + coverage Block merge
security Vulnerability scan Block merge
build Docker image creation Block merge
deploy (CD) Environment deployment Manual approval for prod

Branch Protection (REQUIRED)

# Settings → Branches → Branch protection rules → main

Required checks:
  - lint
  - test
  - security
  - build

Settings:
  - Require a pull request before merging: 
  - Require approvals: 1
  - Dismiss stale approvals: 
  - Require status checks to pass: 
  - Require branches to be up to date: 

Detection Commands

# Find projects without CI config
find . -name "go.mod" -exec dirname {} \; | while read dir; do
  if [ ! -f "$dir/.github/workflows/ci.yml" ] && [ ! -f "$dir/.gitlab-ci.yml" ]; then
    echo "MISSING CI: $dir"
  fi
done

Anti-Rationalization Table

Rationalization Why It's WRONG Required Action
"Local tests are enough" Local ≠ CI environment. CI catches env-specific issues. Add CI pipeline
"Security scan is slow" Slow scan > production vulnerability. Include govulncheck
"We'll add CI later" Later = technical debt. Start with CI. Add CI on project creation
"Manual deployment is fine" Manual = error-prone + no audit trail. Automate deployments

Checklist

Before deploying infrastructure, verify:

  • Terraform state stored remotely with locking
  • All resources tagged appropriately
  • Docker images use multi-stage builds
  • Secrets managed via External Secrets or similar
  • Monitoring dashboards and alerts configured