- name
- pipeline-blueprint
- description
- Provide CI/CD best practices and pipeline templates for GitHub Actions and GitLab CI, recommending configurations based on project type (frontend, backend, fullstack, library, monorepo, mobile).
- license
- MIT
# CI/CD Configuration Best Practices
This skill provides CI/CD pipeline templates and best practices for **GitHub Actions** and **GitLab CI**. It recommends configurations based on project type, helping teams quickly set up reliable, secure, and efficient pipelines.
## How to Use
When a user describes their project (language, framework, deployment target), recommend the most appropriate pipeline template below. Adapt stages, caching strategies, and deployment steps to match their stack.
---
## General Best Practices
### Pipeline Design Principles
1. **Fail fast**: Run linting and unit tests before expensive integration or E2E tests.
2. **Cache aggressively**: Cache dependency directories (`node_modules`, `.pip_cache`, `.m2`, `.gradle`) to speed up builds.
3. **Pin versions**: Pin CI runner images, tool versions, and action versions to SHA or exact tags — never use `latest`.
4. **Least privilege**: Use minimal permissions for tokens and credentials. Prefer OIDC over long-lived secrets where supported.
5. **Parallelize**: Split test suites across parallel jobs. Use matrix builds for multi-version testing.
6. **Immutable artifacts**: Build once, promote the same artifact through staging → production.
7. **Branch protection**: Require CI to pass before merging. Use status checks on the default branch.
### Security Checklist
- Never hardcode secrets in pipeline files; use the platform's secret management (GitHub Secrets / GitLab CI Variables).
- Audit third-party actions/images before use. Prefer official or verified sources.
- Enable dependency scanning (Dependabot, GitLab Dependency Scanning) and SAST where possible.
- Restrict who can trigger production deployments.
- Rotate secrets on a regular cadence.
---
## Project Type Templates
### 1. Frontend (React / Vue / Angular / Static Sites)
**Key stages**: Install → Lint → Test → Build → Deploy
#### GitHub Actions
```yaml
name: Frontend CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
ci:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
needs: ci
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-22.04
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
# Replace with your deployment step (e.g., S3 sync, Cloudflare Pages, Vercel)
- name: Deploy
run: echo "Add your deployment command here"
```
#### GitLab CI
```yaml
stages:
- install
- lint
- test
- build
- deploy
default:
image: node:20-slim
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
install:
stage: install
script:
- npm ci
lint:
stage: lint
script:
- npm run lint
test:
stage: test
script:
- npm test -- --coverage
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
deploy:
stage: deploy
script:
- echo "Add your deployment command here"
environment:
name: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
```
---
### 2. Backend (Node.js / Python / Go / Java)
**Key stages**: Install → Lint → Test → Build → Docker Build → Deploy
#### GitHub Actions (Python example)
```yaml
name: Backend CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
packages: write
jobs:
ci:
runs-on: ubuntu-22.04
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install -r requirements.txt
- run: ruff check .
- run: pytest --cov --cov-report=xml
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
docker:
needs: ci
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
```
#### GitLab CI (Python example)
```yaml
stages:
- test
- build
- deploy
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip_cache"
default:
image: python:3.12-slim
test:
stage: test
services:
- postgres:16
variables:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
DATABASE_URL: postgresql://test:test@postgres:5432/testdb
cache:
key: pip
paths:
- .pip_cache/
script:
- pip install -r requirements.txt
- ruff check .
- pytest --cov --cov-report=xml
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
build-image:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy:
stage: deploy
script:
- echo "Add your deployment command here"
environment:
name: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
```
---
### 3. Library / Package (npm / PyPI / Maven)
**Key stages**: Lint → Test (matrix) → Build → Publish
#### GitHub Actions (npm library example)
```yaml
name: Library CI/CD
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
test:
runs-on: ubuntu-22.04
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
publish:
needs: test
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
cache: npm
- run: npm ci
- run: npm run build
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
#### GitLab CI (PyPI library example)
```yaml
stages:
- test
- publish
test:
stage: test
image: python:${PYTHON_VERSION}-slim
parallel:
matrix:
- PYTHON_VERSION: ["3.10", "3.11", "3.12"]
script:
- pip install -e ".[dev]"
- ruff check .
- pytest
publish:
stage: publish
image: python:3.12-slim
script:
- pip install build twine
- python -m build
- twine upload dist/*
variables:
TWINE_USERNAME: __token__
TWINE_PASSWORD: $PYPI_TOKEN
rules:
- if: $CI_COMMIT_TAG =~ /^v/
```
---
### 4. Fullstack (Frontend + Backend monorepo)
**Key stages**: Detect changes → Run affected pipelines → Integration test → Deploy
#### GitHub Actions
```yaml
name: Fullstack CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
changes:
runs-on: ubuntu-22.04
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-22.04
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
backend:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-22.04
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: backend/requirements.txt
- run: pip install -r requirements.txt
- run: ruff check .
- run: pytest
e2e:
needs: [frontend, backend]
if: always() && !cancelled() && !contains(needs.*.result, 'failure')
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Run E2E tests
run: echo "Add E2E test command (e.g., Playwright, Cypress)"
```
---
### 5. Monorepo (Turborepo / Nx / Lerna)
#### GitHub Actions (Turborepo example)
```yaml
name: Monorepo CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
ci:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx turbo run lint test build --filter='...[HEAD^]'
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
```
---
### 6. Mobile (React Native / Flutter)
#### GitHub Actions (React Native / Android example)
```yaml
name: Mobile CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
android-build:
needs: test
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
cache: gradle
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Build Android
working-directory: android
run: ./gradlew assembleRelease
- uses: actions/upload-artifact@v4
with:
name: android-apk
path: android/app/build/outputs/apk/release/*.apk
```
---
## Advanced Patterns
### Reusable Workflows (GitHub Actions)
Extract common CI logic into reusable workflows to reduce duplication across repositories:
```yaml
在 GitHub 查看