Pipelines CI/CD en 2025 : GitHub Actions, GitLab CI et Jenkins
Guide complet des pipelines CI/CD en 2025 : comparaison GitHub Actions, GitLab CI et Jenkins avec meilleures pratiques et exemples concrets.
GitHub Actions permet de créer des pipelines CI/CD puissants directement dans votre repository. Ce guide couvre l’implémentation complète d’un pipeline moderne avec tests, build, et déploiement multi-environnements.
Workflow (fichier .yml)
└── Jobs (s'exécutent en parallèle par défaut)
└── Steps (s'exécutent séquentiellement)
└── Actions (actions réutilisables)
# Exemple de structure
.github/
└── workflows/
├── ci.yml # Integration continue
├── cd.yml # Deploiement continu
├── security.yml # Scans de sécurité
└── release.yml # Gestion des releases
# .github/workflows/hello.yml
name: Hello World
# Déclencheurs
on:
push:
branches: [main]
pullrequest:
branches: [main]
# Jobs
jobs:
greet:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Say hello
run: echo "Hello, GitHub Actions!"
- name: Show environment
run: |
echo "Runner OS: ${{ runner.os }}"
echo "GitHub ref: ${{ github.ref }}"
echo "GitHub actor: ${{ github.actor }}"
# .github/workflows/ci.yml
name: Continuous Integration
on:
push:
branches: [main, develop]
pullrequest:
branches: [main, develop]
env:
NODEVERSION: '18'
CACHEVERSION: 'v1'
jobs:
# Job 1: Lint et validation
lint:
name: Lint Code
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0 # Nécessaire pour certains linters
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEVERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run Prettier check
run: npm run format:check
- name: TypeScript check
run: npm run type-check
- name: Check commit messages
if: github.eventname == 'pullrequest'
run: |
npm install -g @commitlint/cli @commitlint/config-conventional
npx commitlint --from ${{ github.event.pullrequest.base.sha }} --to HEAD
# Job 2: Tests unitaires
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit -- --coverage --maxWorkers=2
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
flags: unit-tests
name: node-${{ matrix.node-version }}
failciiferror: false
- name: Archive test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results-${{ matrix.node-version }}
path: |
coverage/
test-results/
retention-days: 7
# Job 3: Tests d'integration
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
image: postgres:15
env:
POSTGRESDB: testdb
POSTGRESUSER: testuser
POSTGRESPASSWORD: testpass
options: >-
--health-cmd pgisready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
env:
DATABASEURL: postgresql://testuser:testpass@localhost:5432/testdb
REDISURL: redis://localhost:6379
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEVERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run database migrations
run: npm run db:migrate
- name: Seed test data
run: npm run db:seed
- name: Run integration tests
run: npm run test:integration
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: integration-test-logs
path: logs/
# Job 4: Tests E2E avec Playwright
e2e-tests:
name: E2E Tests
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEVERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Build application
run: npm run build
- name: Start application
run: |
npm run start:test &
npx wait-on http://localhost:3000 --timeout 60000
- name: Run E2E tests
run: npm run test:e2e
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload test videos
if: failure()
uses: actions/upload-artifact@v3
with:
name: e2e-videos
path: test-results//.webm
retention-days: 3
# Job 5: Build
build:
name: Build Application
needs: [lint, unit-tests]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEVERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
env:
CI: true
NODEENV: production
- name: Check bundle size
run: |
npm run analyze:bundle
if [ -f bundle-size.json ]; then
SIZE=$(jq '.totalSize' bundle-size.json)
if [ "$SIZE" -gt 500000 ]; then
echo "::warning::Bundle size ($SIZE bytes) exceeds 500KB"
fi
fi
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ github.sha }}
path: |
build/
dist/
retention-days: 7
# Job 6: Security scans
security:
name: Security Scans
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sariffile: 'trivy-results.sarif'
- name: Run npm audit
run: npm audit --audit-level=moderate
- name: Check for secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.defaultbranch }}
head: HEAD
# Job 7: Code quality
code-quality:
name: Code Quality Analysis
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Full history for SonarCloud
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUBTOKEN: ${{ secrets.GITHUBTOKEN }}
SONARTOKEN: ${{ secrets.SONARTOKEN }}
with:
args: >
-Dsonar.organization=my-org
-Dsonar.projectKey=my-project
-Dsonar.sources=src
-Dsonar.tests=tests
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
# Job final: Status check
ci-success:
name: CI Success
needs: [lint, unit-tests, integration-tests, e2e-tests, build, security]
runs-on: ubuntu-latest
if: always()
steps:
- name: Check all jobs status
run: |
if [[ "${{ needs.lint.result }}" != "success" ]] ||
[[ "${{ needs.unit-tests.result }}" != "success" ]] ||
[[ "${{ needs.integration-tests.result }}" != "success" ]] ||
[[ "${{ needs.e2e-tests.result }}" != "success" ]] ||
[[ "${{ needs.build.result }}" != "success" ]] ||
[[ "${{ needs.security.result }}" != "success" ]]; then
echo "One or more CI jobs failed"
exit 1
fi
echo "All CI jobs passed successfully!"
# .github/workflows/cd.yml
name: Continuous Deployment
on:
push:
branches: [main]
workflowdispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
type: choice
options:
- staging
- production
env:
DOCKERREGISTRY: ghcr.io
IMAGENAME: ${{ github.repository }}
jobs:
# Job 1: Build Docker image
build-image:
name: Build Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERREGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUBTOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERREGISTRY }}/${{ env.IMAGENAME }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILDDATE=${{ github.event.headcommit.timestamp }}
VCSREF=${{ github.sha }}
VERSION=${{ steps.meta.outputs.version }}
- name: Scan image for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.DOCKERREGISTRY }}/${{ env.IMAGENAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-image-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sariffile: 'trivy-image-results.sarif'
# Job 2: Deploy to Staging
deploy-staging:
name: Deploy to Staging
needs: build-image
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'staging'
environment:
name: staging
url: https://staging.example.com
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWSACCESSKEYID }}
aws-secret-access-key: ${{ secrets.AWSSECRETACCESSKEY }}
aws-region: us-east-1
- name: Deploy to ECS
run: |
# Update task definition
TASKDEFINITION=$(aws ecs describe-task-definition
--task-definition staging-app
--query 'taskDefinition'
--output json)
NEWTASKDEF=$(echo $TASKDEFINITION | jq
--arg IMAGE "${{ env.DOCKERREGISTRY }}/${{ env.IMAGENAME }}:${{ github.sha }}"
'.containerDefinitions[0].image = $IMAGE | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)')
NEWTASKINFO=$(aws ecs register-task-definition
--cli-input-json "$NEWTASKDEF")
NEWREVISION=$(echo $NEWTASKINFO | jq -r '.taskDefinition.revision')
# Update service
aws ecs update-service
--cluster staging-cluster
--service staging-app-service
--task-definition staging-app:${NEWREVISION}
--force-new-deployment
# Wait for deployment
aws ecs wait services-stable
--cluster staging-cluster
--services staging-app-service
- name: Run smoke tests
run: |
npm ci
npm run test:smoke -- --baseUrl=https://staging.example.com
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: |
Deployment to staging: ${{ job.status }}
Commit: ${{ github.event.headcommit.message }}
Author: ${{ github.actor }}
webhookurl: ${{ secrets.SLACKWEBHOOK }}
# Job 3: Deploy to Production
deploy-production:
name: Deploy to Production
needs: deploy-staging
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'production'
environment:
name: production
url: https://example.com
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWSACCESSKEYIDPROD }}
aws-secret-access-key: ${{ secrets.AWSSECRETACCESSKEYPROD }}
aws-region: us-east-1
- name: Create deployment snapshot
run: |
# Backup current production state
aws ecs describe-services
--cluster production-cluster
--services production-app-service
> deployment-backup-${{ github.sha }}.json
- name: Blue/Green Deployment
run: |
# Deploy to green environment
# ... (similar to staging deployment)
# Run health checks
for i in {1..30}; do
if curl -f https://green.example.com/health; then
echo "Green environment healthy"
break
fi
sleep 10
done
# Switch traffic (update load balancer)
aws elbv2 modify-listener
--listener-arn ${{ secrets.PRODLISTENERARN }}
--default-actions Type=forward,TargetGroupArn=${{ secrets.GREENTARGETGROUP }}
# Monitor for 5 minutes
sleep 300
# Check error rate
ERRORRATE=$(aws cloudwatch get-metric-statistics
--namespace AWS/ApplicationELB
--metric-name HTTPCodeTarget5XXCount
--start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%S)
--end-time $(date -u +%Y-%m-%dT%H:%M:%S)
--period 300
--statistics Sum
--query 'Datapoints[0].Sum'
--output text)
if [ "$ERRORRATE" -gt 10 ]; then
echo "High error rate detected, rolling back"
# Rollback logic
aws elbv2 modify-listener
--listener-arn ${{ secrets.PRODLISTENERARN }}
--default-actions Type=forward,TargetGroupArn=${{ secrets.BLUETARGETGROUP }}
exit 1
fi
- name: Run production smoke tests
run: |
npm run test:smoke -- --baseUrl=https://example.com
- name: Create GitHub Release
if: success()
uses: actions/create-release@v1
env:
GITHUBTOKEN: ${{ secrets.GITHUBTOKEN }}
with:
tagname: v${{ github.runnumber }}
releasename: Release ${{ github.runnumber }}
body: |
Deployed commit: ${{ github.sha }}
Deployment time: ${{ github.event.headcommit.timestamp }}
- name: Notify deployment
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: |
Production deployment: ${{ job.status }}
Version: v${{ github.runnumber }}
URL: https://example.com
webhookurl: ${{ secrets.SLACKWEBHOOKPROD }}
# .github/workflows/multi-platform.yml
name: Multi-Platform Build
on: [push, pullrequest]
jobs:
build:
name: Build on ${{ matrix.os }} - ${{ matrix.node }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [16, 18, 20]
include:
- os: ubuntu-latest
node: 20
experimental: true
exclude:
- os: windows-latest
node: 16
steps:
- uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.node }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
# .github/workflows/production-deploy.yml
name: Production Deployment with Approval
on:
workflowdispatch:
inputs:
version:
description: 'Version to deploy'
required: true
reason:
description: 'Deployment reason'
required: true
jobs:
request-approval:
name: Request Deployment Approval
runs-on: ubuntu-latest
steps:
- name: Create approval issue
uses: actions/github-script@v6
with:
script: |
const issue = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: Deployment Approval: v${{ github.event.inputs.version }},
body: `
## Deployment Request
Version: ${{ github.event.inputs.version }}
Reason: ${{ github.event.inputs.reason }}
Requested by: @${{ github.actor }}
Time: ${new Date().toISOString()}
### Approval Process
Team leads, please review and approve by commenting:
/approve
Or reject with:
/reject [reason]
`,
labels: ['deployment', 'approval-required']
});
core.setOutput('issuenumber', issue.data.number);
wait-for-approval:
name: Wait for Approval
needs: request-approval
runs-on: ubuntu-latest
timeout-minutes: 1440 # 24 hours
steps:
- name: Wait for approval comment
uses: actions/github-script@v6
with:
script: |
const issuenumber = ${{ needs.request-approval.outputs.issuenumber }};
while (true) {
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issuenumber: issuenumber
});
for (const comment of comments.data) {
if (comment.body.includes('/approve')) {
console.log('Deployment approved!');
return;
}
if (comment.body.includes('/reject')) {
throw new Error('Deployment rejected');
}
}
await new Promise(resolve => setTimeout(resolve, 30000)); // Wait 30s
}
deploy:
name: Deploy to Production
needs: wait-for-approval
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: Deploy
run: |
echo "Deploying version ${{ github.event.inputs.version }}"
# Deployment logic here
# .github/workflows/optimized-cache.yml
name: Optimized Build with Caching
on: [push, pullrequest]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
# Cache npm dependencies
- name: Cache npm dependencies
uses: actions/cache@v3
id: npm-cache
with:
path: |
~/.npm
nodemodules
key: ${{ runner.os }}-npm-${{ hashFiles('/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
if: steps.npm-cache.outputs.cache-hit != 'true'
run: npm ci
# Cache build output
- name: Cache build
uses: actions/cache@v3
id: build-cache
with:
path: |
dist/
.next/cache
key: ${{ runner.os }}-build-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-
- name: Build
if: steps.build-cache.outputs.cache-hit != 'true'
run: npm run build
# Cache Playwright browsers
- name: Cache Playwright browsers
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('/package-lock.json') }}
- name: Test
run: npm test
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy Workflow
on:
workflowcall:
inputs:
environment:
required: true
type: string
image-tag:
required: true
type: string
secrets:
aws-access-key:
required: true
aws-secret-key:
required: true
slack-webhook:
required: false
jobs:
deploy:
name: Deploy to ${{ inputs.environment }}
runs-on: ubuntu-latest
environment:
name: ${{ inputs.environment }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.aws-access-key }}
aws-secret-access-key: ${{ secrets.aws-secret-key }}
aws-region: us-east-1
- name: Deploy
run: |
echo "Deploying ${{ inputs.image-tag }} to ${{ inputs.environment }}"
# Deployment commands
- name: Notify
if: secrets.slack-webhook != ''
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
webhookurl: ${{ secrets.slack-webhook }}
# .github/workflows/main-deploy.yml
name: Main Deployment Pipeline
on:
push:
branches: [main]
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
image-tag: ${{ github.sha }}
secrets:
aws-access-key: ${{ secrets.AWSKEYSTAGING }}
aws-secret-key: ${{ secrets.AWSSECRETSTAGING }}
slack-webhook: ${{ secrets.SLACKWEBHOOK }}
deploy-production:
needs: deploy-staging
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
image-tag: ${{ github.sha }}
secrets:
aws-access-key: ${{ secrets.AWSKEYPROD }}
aws-secret-key: ${{ secrets.AWSSECRETPROD }}
slack-webhook: ${{ secrets.SLACKWEBHOOKPROD }}
# .github/actions/setup-app/action.yml
name: 'Setup Application'
description: 'Setup Node.js and install dependencies'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '18'
cache-key:
description: 'Custom cache key'
required: false
outputs:
cache-hit:
description: 'Whether cache was hit'
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node-version }}
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: nodemodules
key: ${{ inputs.cache-key || format('deps-{0}-{1}', runner.os, hashFiles('/package-lock.json')) }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: npm ci
- name: Verify installation
shell: bash
run: |
node --version
npm --version
# .github/workflows/use-composite.yml
name: Use Composite Action
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup application
uses: ./.github/actions/setup-app
with:
node-version: '18'
- name: Run tests
run: npm test
# .github/workflows/debug.yml
name: Debug Workflow
on:
workflowdispatch:
inputs:
debugenabled:
description: 'Enable debug logging'
type: boolean
default: false
jobs:
debug-job:
runs-on: ubuntu-latest
steps:
- name: Enable debug logging
if: ${{ inputs.debugenabled }}
run: |
echo "ACTIONSSTEPDEBUG=true" >> $GITHUBENV
echo "ACTIONSRUNNERDEBUG=true" >> $GITHUBENV
- name: Checkout
uses: actions/checkout@v3
- name: Show context
run: |
echo "GitHub context:"
echo "${{ toJSON(github) }}"
echo -e "nRunner context:"
echo "${{ toJSON(runner) }}"
echo -e "nJob context:"
echo "${{ toJSON(job) }}"
- name: Debug environment
run: |
echo "Environment variables:"
env | sort
echo -e "nSystem info:"
uname -a
df -h
free -h
- name: Test with debug
run: |
if [ "${{ inputs.debugenabled }}" == "true" ]; then
npm test -- --verbose --debug
else
npm test
fi
# .github/workflows/performance-monitoring.yml
name: Workflow Performance Monitoring
on:
workflowrun:
workflows: ["CI", "CD"]
types: [completed]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- name: Get workflow run data
uses: actions/github-script@v6
with:
script: |
const run = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
runid: context.payload.workflowrun.id
});
const jobs = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
runid: context.payload.workflowrun.id
});
const duration = new Date(run.data.updatedat) - new Date(run.data.createdat);
const durationMinutes = Math.floor(duration / 60000);
console.log(Workflow: ${run.data.name});
console.log(Duration: ${durationMinutes} minutes);
console.log(Status: ${run.data.conclusion});
for (const job of jobs.data.jobs) {
const jobDuration = new Date(job.completedat) - new Date(job.startedat);
console.log(Job ${job.name}: ${Math.floor(jobDuration / 1000)}s);
}
// Alert if workflow is too slow
if (durationMinutes > 30) {
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: Slow workflow detected: ${run.data.name},
body: Workflow took ${durationMinutes} minutes to complete.,
labels: ['performance', 'ci']
});
}
# Bonne pratique pour les secrets
steps:
- name: Use secrets securely
env:
# NEVER: echo ${{ secrets.APIKEY }}
APIKEY: ${{ secrets.APIKEY }}
DBPASSWORD: ${{ secrets.DBPASSWORD }}
run: |
# Les secrets sont maintenant dans l'environnement
./deploy.sh
- name: Mask sensitive data
run: |
echo "::add-mask::$SENSITIVEVALUE"
echo "Value: $SENSITIVEVALUE" # Sera masqué dans les logs
# Via GitHub CLI
gh api repos/:owner/:repo/environments/production -X PUT --input - << EOF
{
"waittimer": 30,
"reviewers": [
{
"type": "User",
"id": 12345
},
{
"type": "Team",
"id": 67890
}
],
"deploymentbranchpolicy": {
"protectedbranches": true,
"custombranchpolicies": false
}
}
EOF
# Optimisations pour réduire les coûts
jobs:
optimize:
runs-on: ubuntu-latest
# Timeout pour éviter les jobs bloqués
timeout-minutes: 30
steps:
# Checkout superficiel
- uses: actions/checkout@v3
with:
fetch-depth: 1
# Installer seulement les dépendances de production
- name: Install production dependencies
run: npm ci --production
# Utiliser des caches agressifs
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('/package-lock.json') }}
# Paralléliser quand possible
- name: Run parallel tests
run: npm test -- --maxWorkers=4
# Annuler les runs redondants
- name: Cancel redundant runs
uses: styfle/cancel-workflow-action@0.11.0
with:
accesstoken: ${{ github.token }}
# 1. Activer le debug logging
# Settings > Secrets > New repository secret
# Name: ACTIONSSTEPDEBUG
# Value: true
# 2. Re-run le workflow avec debug
gh run rerun --debug
# 3. Voir les logs détaillés
gh run view --log
# 4. Télécharger les logs
gh run download
# 5. Tester localement avec act
# Installation: https://github.com/nektos/act
act -j build -s GITHUB TOKEN=
# 6. Dry-run
act -n
# 7. Lister les jobs
act -l
# 8. Runner spécifique job
act -j test
# Problème: Timeout sur les tests
# Solution: Augmenter le timeout
jobs:
test:
timeout-minutes: 60 # Au lieu de défaut (360)
# Problème: Cache non utilisé
# Solution: Vérifier les clés
- uses: actions/cache@v3
with:
path: nodemodules
key: deps-${{ hashFiles('package-lock.json') }} # Pas / si fichier à la racine
# Problème: Secrets non disponibles dans PR de fork
# Solution: Utiliser pullrequesttarget avec précaution
on:
pullrequesttarget: # A accès aux secrets
types: [labeled]
jobs:
build:
if: contains(github.event.pullrequest.labels..name, 'safe-to-test')
# ...
# Problème: Artifacts expirés
# Solution: Augmenter la rétention
- uses: actions/upload-artifact@v3
with:
name: build
path: dist/
retention-days: 30 # Au lieu de défaut (90)
{
"scripts": {
"test": "jest",
"test:unit": "jest --testPathPattern=unit",
"test:integration": "jest --testPathPattern=integration",
"test:e2e": "playwright test",
"test:smoke": "playwright test --grep @smoke",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"type-check": "tsc --noEmit",
"build": "webpack --mode production",
"analyze:bundle": "webpack-bundle-analyzer dist/stats.json",
"db:migrate": "knex migrate:latest",
"db:seed": "knex seed:run"
}
}
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production &&
npm cache clean --force
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -g 1001 -S nodejs &&
adduser -S nodejs -u 1001
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/nodemodules ./nodemodules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
USER nodejs
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3
CMD node healthcheck.js
CMD ["node", "dist/server.js"]
GitHub Actions offre une plateforme CI/CD puissante et flexible. Les points clés :
Avec ces patterns, vous pouvez construire des pipelines robustes et efficaces pour tout type de projet.
Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.