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
- Read Phase 5 Constitution -
constitution-prompt-phase-5.md - Create workflows directory -
.github/workflows/ - Set up CI pipeline - Test, lint, build on PR
- Set up CD pipeline - Deploy to staging/production
- Configure secrets - Docker registry, K8s credentials
- 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?