Agent skill

github-actions-cicd

Set up GitHub Actions CI/CD pipelines for testing, building, and deploying to Kubernetes. Use when implementing continuous integration and deployment for Phase 5. (project)

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/github-actions-cicd

SKILL.md

GitHub Actions CI/CD Skill

Quick Start

  1. Read Phase 5 Constitution - constitution-prompt-phase-5.md
  2. Create workflows directory - .github/workflows/
  3. Set up CI pipeline - Test, lint, build on PR
  4. Set up CD pipeline - Deploy to staging/production
  5. Configure secrets - Docker registry, K8s credentials
  6. Add branch protection - Require CI to pass

Workflow Structure

.github/
└── workflows/
    ├── ci.yaml           # Test & build on PR
    ├── cd-staging.yaml   # Deploy to staging
    ├── cd-production.yaml # Deploy to production
    └── security.yaml     # Security scanning

CI Pipeline (Pull Requests)

Create .github/workflows/ci.yaml:

yaml
name: CI Pipeline

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

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

jobs:
  # Backend Tests
  backend-test:
    name: Backend Tests
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./backend

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

    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v4
        with:
          version: "latest"

      - name: Set up Python
        run: uv python install 3.13

      - name: Install dependencies
        run: uv sync --frozen

      - name: Run linting
        run: uv run ruff check .

      - name: Run type checking
        run: uv run mypy src/

      - name: Run tests
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/todo_test
          BETTER_AUTH_SECRET: test-secret
          GEMINI_API_KEY: test-key
        run: uv run pytest tests/ -v --cov=src --cov-report=xml

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          files: ./backend/coverage.xml
          flags: backend

  # Frontend Tests
  frontend-test:
    name: Frontend Tests
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./frontend

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: './frontend/package-lock.json'

      - name: Install dependencies
        run: npm ci

      - name: Run linting
        run: npm run lint

      - name: Run type checking
        run: npm run type-check

      - name: Run tests
        run: npm test -- --coverage

      - name: Build
        run: npm run build
        env:
          NEXT_PUBLIC_API_URL: http://localhost:8000

  # Build Docker Images
  build-images:
    name: Build Images
    runs-on: ubuntu-latest
    needs: [backend-test, frontend-test]
    if: github.event_name == 'push'

    permissions:
      contents: read
      packages: write

    strategy:
      matrix:
        service:
          - name: backend
            context: ./backend
            dockerfile: ./backend/Dockerfile
          - name: frontend
            context: ./frontend
            dockerfile: ./frontend/Dockerfile
          - name: notification-service
            context: ./services/notification
            dockerfile: ./services/notification/Dockerfile
          - name: recurring-service
            context: ./services/recurring
            dockerfile: ./services/recurring/Dockerfile

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        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_PREFIX }}/${{ matrix.service.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: ${{ matrix.service.context }}
          file: ${{ matrix.service.dockerfile }}
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # Helm Chart Validation
  helm-lint:
    name: Helm Lint
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Helm
        uses: azure/setup-helm@v4
        with:
          version: v3.14.0

      - name: Lint charts
        run: helm lint ./helm/evolution-todo

      - name: Template validation
        run: |
          helm template evolution-todo ./helm/evolution-todo \
            --values ./helm/evolution-todo/values.yaml \
            --debug

CD Pipeline (Staging)

Create .github/workflows/cd-staging.yaml:

yaml
name: CD Staging

on:
  push:
    branches: [develop]
  workflow_dispatch:

env:
  REGISTRY: ghcr.io
  IMAGE_PREFIX: ${{ github.repository }}
  CLUSTER_NAME: todo-staging
  NAMESPACE: todo-staging

jobs:
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    environment: staging

    steps:
      - uses: actions/checkout@v4

      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}

      - name: Save DigitalOcean kubeconfig
        run: doctl kubernetes cluster kubeconfig save ${{ env.CLUSTER_NAME }}

      - name: Set up Helm
        uses: azure/setup-helm@v4
        with:
          version: v3.14.0

      - name: Deploy with Helm
        run: |
          helm upgrade --install evolution-todo ./helm/evolution-todo \
            --namespace ${{ env.NAMESPACE }} \
            --create-namespace \
            --values ./helm/evolution-todo/values-staging.yaml \
            --set global.image.tag=${{ github.sha }} \
            --set backend.env.DATABASE_URL=${{ secrets.STAGING_DATABASE_URL }} \
            --set backend.env.GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }} \
            --set backend.env.BETTER_AUTH_SECRET=${{ secrets.BETTER_AUTH_SECRET }} \
            --wait \
            --timeout 10m

      - name: Verify deployment
        run: |
          kubectl rollout status deployment/backend -n ${{ env.NAMESPACE }}
          kubectl rollout status deployment/frontend -n ${{ env.NAMESPACE }}

      - name: Run smoke tests
        run: |
          FRONTEND_URL=$(kubectl get svc frontend -n ${{ env.NAMESPACE }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
          curl -f http://$FRONTEND_URL/health || exit 1

CD Pipeline (Production)

Create .github/workflows/cd-production.yaml:

yaml
name: CD Production

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to deploy'
        required: true

env:
  REGISTRY: ghcr.io
  IMAGE_PREFIX: ${{ github.repository }}
  CLUSTER_NAME: todo-production
  NAMESPACE: todo-app

jobs:
  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    environment: production

    steps:
      - uses: actions/checkout@v4

      - name: Determine version
        id: version
        run: |
          if [ "${{ github.event_name }}" == "release" ]; then
            echo "version=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
          else
            echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
          fi

      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}

      - name: Save DigitalOcean kubeconfig
        run: doctl kubernetes cluster kubeconfig save ${{ env.CLUSTER_NAME }}

      - name: Set up Helm
        uses: azure/setup-helm@v4
        with:
          version: v3.14.0

      - name: Deploy with Helm
        run: |
          helm upgrade --install evolution-todo ./helm/evolution-todo \
            --namespace ${{ env.NAMESPACE }} \
            --values ./helm/evolution-todo/values-production.yaml \
            --set global.image.tag=${{ steps.version.outputs.version }} \
            --set backend.env.DATABASE_URL=${{ secrets.PROD_DATABASE_URL }} \
            --set backend.env.GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }} \
            --set backend.env.BETTER_AUTH_SECRET=${{ secrets.BETTER_AUTH_SECRET }} \
            --set kafka.brokers=${{ secrets.REDPANDA_BROKERS }} \
            --wait \
            --timeout 15m

      - name: Verify deployment
        run: |
          kubectl rollout status deployment/backend -n ${{ env.NAMESPACE }} --timeout=5m
          kubectl rollout status deployment/frontend -n ${{ env.NAMESPACE }} --timeout=5m

      - name: Notify success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "Production deployment successful: ${{ steps.version.outputs.version }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

      - name: Rollback on failure
        if: failure()
        run: |
          helm rollback evolution-todo -n ${{ env.NAMESPACE }}

Security Scanning

Create .github/workflows/security.yaml:

yaml
name: Security Scanning

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 0'  # Weekly on Sunday

jobs:
  trivy-scan:
    name: Trivy Vulnerability Scan
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Run Trivy vulnerability scanner (repo)
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'

      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: 'trivy-results.sarif'

  dependency-review:
    name: Dependency Review
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'

    steps:
      - uses: actions/checkout@v4

      - name: Dependency Review
        uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high

Required Secrets

Configure in GitHub Settings → Secrets and variables → Actions:

Secret Description Used In
DIGITALOCEAN_ACCESS_TOKEN DO API token CD pipelines
STAGING_DATABASE_URL Staging Neon DB URL CD Staging
PROD_DATABASE_URL Production Neon DB URL CD Production
GEMINI_API_KEY Google AI API key All deployments
BETTER_AUTH_SECRET Auth secret All deployments
REDPANDA_BROKERS Redpanda Cloud brokers Production
SLACK_WEBHOOK Slack notifications CD Production

Branch Protection Rules

Configure in GitHub Settings → Branches → Branch protection rules:

yaml
# For main branch
- Require pull request before merging
- Require status checks to pass:
  - backend-test
  - frontend-test
  - helm-lint
- Require branches to be up to date
- Require signed commits (optional)
- Include administrators

Verification Checklist

  • .github/workflows/ directory created
  • CI pipeline runs on PRs
  • Docker images build and push to GHCR
  • Helm charts lint successfully
  • Staging deployment works
  • Production deployment works
  • All secrets configured
  • Branch protection rules set
  • Security scanning enabled

Troubleshooting

Issue Cause Solution
Tests fail Missing env vars Add to workflow env
Push denied No GHCR permission Check packages: write
Helm deploy fails Bad values Run helm template locally
K8s auth fails Bad kubeconfig Check doctl token

References

Didn't find tool you were looking for?

Be as detailed as possible for better results