Claude-skill-registry 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)
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/github-actions-cicd" ~/.claude/skills/majiayu000-claude-skill-registry-github-actions-cicd && rm -rf "$T"
manifest:
skills/data/github-actions-cicd/SKILL.mdsource content
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:
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:
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:
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:
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 |
|---|---|---|
| DO API token | CD pipelines |
| Staging Neon DB URL | CD Staging |
| Production Neon DB URL | CD Production |
| Google AI API key | All deployments |
| Auth secret | All deployments |
| Redpanda Cloud brokers | Production |
| Slack notifications | CD Production |
Branch Protection Rules
Configure in GitHub Settings → Branches → Branch protection rules:
# 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
-
directory created.github/workflows/ - 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 |
| Helm deploy fails | Bad values | Run locally |
| K8s auth fails | Bad kubeconfig | Check doctl token |