Docker en 2025 : 42 Meilleures Pratiques de Production
Guide complet des meilleures pratiques Docker pour 2025 : sécurité, performance et optimisation des images pour la production.
Guide complet des pipelines CI/CD en 2025 : comparaison GitHub Actions, GitLab CI et Jenkins avec meilleures pratiques et exemples concrets.
L’intégration continue et le déploiement continu (CI/CD) sont devenus le cœur battant du développement logiciel moderne. En 2025, le paysage des outils CI/CD a considérablement évolué, avec GitHub Actions et GitLab CI qui gagnent du terrain face à Jenkins, le vétéran du domaine. Ce guide complet vous aide à choisir le bon outil et à implémenter des pipelines robustes et efficaces.
Selon l’enquête JetBrains 2025, de nombreuses entreprises utilisent encore des produits hérités comme Azure DevOps ou Jenkins tout en migrant vers GitHub Actions ou GitLab CI, parfois sur des périodes de plusieurs mois ou années.
Tendances principales :
Pour les startups utilisant GitHub, GitHub Actions est la solution la plus simple. Pour les grandes entreprises avec des systèmes legacy, Jenkins offre une flexibilité maximale. Pour les équipes entièrement sur GitLab, GitLab CI/CD est le meilleur choix.
| Critère | GitHub Actions | GitLab CI | Jenkins |
|---|---|---|---|
| Hébergement | Cloud (gratuit/payant) | Cloud ou Self-hosted | Self-hosted |
| Configuration | YAML (.github/workflows/) | YAML (.gitlab-ci.yml) | Jenkinsfile ou UI |
| Marketplace | 20,000+ actions | Limité | 1,800+ plugins |
| Intégration | Native GitHub | Native GitLab | Plugins requis |
| Coût startup | Gratuit (2000 min/mois) | Gratuit (400 min/mois) | Infrastructure uniquement |
| Courbe d’apprentissage | Facile | Facile | Moyenne/Élevée |
| Kubernetes | Via actions tierces | Natif (Auto DevOps) | Via plugins |
| Sécurité | Secrets managés | SAST/DAST intégrés | Plugins requis |
GitHub Actions est intégré directement dans GitHub, utilisant des fichiers de workflow basés sur YAML stockés dans le repository, permettant des configurations de pipeline versionnées.
Les équipes ayant migré de Jenkins self-hosted vers GitHub Actions rapportent : « Way easier to maintain. Way easier to implement your pipelines. » (Source: JetBrains Survey 2025)
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pullrequest:
branches: [main]
env:
NODEVERSION: '20'
REGISTRY: ghcr.io
IMAGENAME: ${{ github.repository }}
jobs:
# Job 1: Tests et Linting
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 21]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test -- --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOVTOKEN }}
files: ./coverage/lcov.info
# Job 2: Security Scan
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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'
# Job 3: Build et Push Docker
build:
needs: [test, security]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUBTOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGENAME }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGENAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGENAME }}:buildcache,mode=max
# Job 4: Deploy to Kubernetes
deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup kubectl
uses: azure/setup-kubectl@v3
- name: Set Kubernetes context
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/myapp
myapp=${{ env.REGISTRY }}/${{ env.IMAGENAME }}:${{ github.sha }}
-n production
kubectl rollout status deployment/myapp -n production
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment to production: ${{ job.status }}'
webhookurl: ${{ secrets.SLACKWEBHOOK }}
Actions réutilisables :
# .github/workflows/reusable-build.yml
name: Reusable Build Workflow
on:
workflowcall:
inputs:
environment:
required: true
type: string
secrets:
docker-username:
required: true
docker-password:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build for ${{ inputs.environment }}
run: npm run build:${{ inputs.environment }}
# Utilisation dans un autre workflow
jobs:
build-prod:
uses: ./.github/workflows/reusable-build.yml
with:
environment: production
secrets:
docker-username: ${{ secrets.DOCKERUSERNAME }}
docker-password: ${{ secrets.DOCKERPASSWORD }}
GitLab est une plateforme DevOps complète, avec le CI/CD intégré comme l’une de ses fonctionnalités les plus puissantes. GitLab utilise un seul fichier déclaratif (.gitlab-ci.yml) pour définir l’ensemble du cycle de vie du développement logiciel, offrant une transparence, une auditabilité et une facilité de maintenance inégalées.
.gitlab-ci.ymlGitLab excelle dans l’intégration de la sécurité avec Auto DevOps, tirant parti de jobs pré-configurés pour les tests de sécurité SAST et DAST.
# .gitlab-ci.yml
stages:
- build
- test
- security
- deploy
variables:
DOCKERDRIVER: overlay2
DOCKERTLSCERTDIR: "/certs"
REGISTRYIMAGE: $CIREGISTRYIMAGE
KUBERNETESVERSION: "1.28"
# Template pour build Docker
.docker-build:
image: docker:24-dind
services:
- docker:24-dind
beforescript:
- docker login -u $CIREGISTRYUSER -p $CIREGISTRYPASSWORD $CIREGISTRY
# Job : Build Application
build:
extends: .docker-build
stage: build
script:
- docker build
--build-arg BUILDDATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
--build-arg VCSREF=$CICOMMITSHORTSHA
--cache-from $REGISTRYIMAGE:latest
-t $REGISTRYIMAGE:$CICOMMITSHA
-t $REGISTRYIMAGE:latest
.
- docker push $REGISTRYIMAGE:$CICOMMITSHA
- docker push $REGISTRYIMAGE:latest
only:
- branches
# Job : Tests Unitaires
unit-tests:
stage: test
image: node:20-alpine
cache:
key: ${CICOMMITREFSLUG}
paths:
- nodemodules/
script:
- npm ci
- npm run test:unit -- --coverage
coverage: '/Statementss:s(d+.d+)%/'
artifacts:
reports:
coveragereport:
coverageformat: cobertura
path: coverage/cobertura-coverage.xml
paths:
- coverage/
# Job : Tests d'Intégration
integration-tests:
stage: test
image: $REGISTRYIMAGE:$CICOMMITSHA
services:
- name: postgres:16-alpine
alias: database
- name: redis:7-alpine
alias: cache
variables:
DATABASEURL: "postgresql://user:pass@database:5432/testdb"
REDISURL: "redis://cache:6379"
script:
- npm run test:integration
only:
- mergerequests
- main
# Job : SAST (Static Application Security Testing)
sast:
stage: security
include:
- template: Security/SAST.gitlab-ci.yml
# Job : Dependency Scanning
dependencyscanning:
stage: security
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
# Job : Container Scanning
containerscanning:
stage: security
image: registry.gitlab.com/security-products/container-scanning:latest
variables:
CIAPPLICATIONREPOSITORY: $REGISTRYIMAGE
CIAPPLICATIONTAG: $CICOMMITSHA
script:
- gtcs scan
artifacts:
reports:
containerscanning: gl-container-scanning-report.json
only:
- main
# Job : Deploy to Staging
deploy:staging:
stage: deploy
image: bitnami/kubectl:$KUBERNETESVERSION
environment:
name: staging
url: https://staging.monapp.com
onstop: stop:staging
script:
- kubectl config use-context $KUBECONTEXT
- |
cat <COMMITSHORTSHA
spec:
containers:
- name: myapp
image: $REGISTRYIMAGE:$CICOMMITSHA
ports:
- containerPort: 3000
env:
- name: ENVIRONMENT
value: "staging"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
EOF
- kubectl rollout status deployment/myapp -n staging
only:
- main
# Job : Deploy to Production
deploy:production:
stage: deploy
image: bitnami/kubectl:$KUBERNETESVERSION
environment:
name: production
url: https://monapp.com
script:
- kubectl config use-context $KUBECONTEXTPROD
- kubectl set image deployment/myapp
myapp=$REGISTRYIMAGE:$CICOMMITSHA
-n production
- kubectl rollout status deployment/myapp -n production
when: manual # Déploiement manuel requis
only:
- main
# Job : Arrêt environnement staging
stop:staging:
stage: deploy
image: bitnami/kubectl:$KUBERNETESVERSION
environment:
name: staging
action: stop
script:
- kubectl delete deployment myapp -n staging
when: manual
only:
- main
GitLab peut automatiquement détecter votre langage et configurer un pipeline complet :
# .gitlab-ci.yml minimal avec Auto DevOps
include:
- template: Auto-DevOps.gitlab-ci.yml
variables:
AUTODEVOPSPLATFORMTARGET: ECS # ou K8S, FARGATE
Ce fichier unique active :
Jenkins est l’un des outils CI/CD les plus anciens et les plus utilisés, un serveur d’automatisation open-source qui aide à automatiser la construction, le test et le déploiement des applications. Il utilise des plugins pour s’intégrer avec quasiment toutes les technologies : Docker, AWS, Kubernetes, GitHub, Slack, etc.
Cependant, des outils comme Jenkins nécessitent une configuration significative, donc les services managés comme GitHub Actions ou GitLab CI offrent des configurations plus simples.
// Jenkinsfile déclaratif
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: docker
image: docker:24-dind
command: ['cat']
tty: true
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
- name: kubectl
image: bitnami/kubectl:1.28
command: ['cat']
tty: true
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
'''
}
}
environment {
DOCKERREGISTRY = 'docker.io'
DOCKERCREDENTIALS = credentials('docker-hub-credentials')
IMAGENAME = "monentreprise/monapp"
GITCOMMITSHORT = sh(
returnStdout: true,
script: 'git rev-parse --short HEAD'
).trim()
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
timeout(time: 1, unit: 'HOURS')
disableConcurrentBuilds()
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
env.GITCOMMITMSG = sh(
returnStdout: true,
script: 'git log -1 --pretty=%B'
).trim()
}
}
}
stage('Build') {
steps {
container('docker') {
sh '''
docker build
--build-arg BUILDDATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
--build-arg VCSREF=$GITCOMMITSHORT
--cache-from $IMAGENAME:latest
-t $IMAGENAME:$GITCOMMITSHORT
-t $IMAGENAME:latest
.
'''
}
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
steps {
container('docker') {
sh '''
docker run --rm
$IMAGENAME:$GITCOMMITSHORT
npm test
'''
}
}
}
stage('Security Scan') {
steps {
container('docker') {
sh '''
docker run --rm
-v /var/run/docker.sock:/var/run/docker.sock
aquasec/trivy image
--exit-code 1
--severity HIGH,CRITICAL
$IMAGENAME:$GITCOMMITSHORT
'''
}
}
}
}
}
stage('Push to Registry') {
when {
branch 'main'
}
steps {
container('docker') {
sh '''
echo $DOCKERCREDENTIALSPSW |
docker login -u $DOCKERCREDENTIALSUSR --password-stdin $DOCKERREGISTRY
docker push $IMAGENAME:$GITCOMMITSHORT
docker push $IMAGENAME:latest
'''
}
}
}
stage('Deploy to Staging') {
when {
branch 'main'
}
steps {
container('kubectl') {
sh '''
kubectl set image deployment/myapp
myapp=$IMAGENAME:$GITCOMMITSHORT
-n staging
kubectl rollout status deployment/myapp -n staging
'''
}
}
}
stage('Approval') {
when {
branch 'main'
}
steps {
timeout(time: 24, unit: 'HOURS') {
input message: 'Deploy to production?',
ok: 'Deploy',
submitter: 'admin,devops-team'
}
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
container('kubectl') {
sh '''
kubectl set image deployment/myapp
myapp=$IMAGENAME:$GITCOMMITSHORT
-n production
kubectl rollout status deployment/myapp -n production
'''
}
}
}
}
post {
success {
slackSend(
color: 'good',
message: "✅ Build SUCCESS: ${env.JOBNAME} #${env.BUILDNUMBER}nCommit: ${env.GITCOMMITMSG}"
)
}
failure {
slackSend(
color: 'danger',
message: "❌ Build FAILED: ${env.JOBNAME} #${env.BUILDNUMBER}nCommit: ${env.GITCOMMITMSG}"
)
}
always {
cleanWs()
}
}
}
Exécutez les tests unitaires en premier pour détecter rapidement les erreurs. Les tests rapides (linting, unit tests) doivent s’exécuter avant les tests lents (intégration, E2E).
# Ordre optimal des stages
stages:
- lint # < 1 minute
- unit-test # 2-5 minutes
- build # 3-10 minutes
- integration # 5-15 minutes
- e2e # 10-30 minutes
- deploy # Variable
Mettez en cache les dépendances pour accélérer les builds. Les pipelines peuvent passer de 10 minutes à 2 minutes avec un bon cache.
GitHub Actions :
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Cache automatique
GitLab CI :
cache:
key: ${CICOMMITREFSLUG}
paths:
- nodemodules/
- .npm/
Jenkins :
stage('Install') {
steps {
cache(maxCacheSize: 500, caches: [
arbitraryFileCache(
path: 'nodemodules',
cacheValidityDecidingFile: 'package-lock.json'
)
]) {
sh 'npm ci'
}
}
}
Utilisez Testcontainers pour des environnements de test reproductibles avec bases de données, caches, etc.
// Exemple avec Testcontainers
const { GenericContainer } = require('testcontainers');
describe('Integration Tests', () => {
let postgresContainer;
let redisContainer;
beforeAll(async () => {
postgresContainer = await new GenericContainer('postgres:16-alpine')
.withEnvironment({ POSTGRESPASSWORD: 'test' })
.withExposedPorts(5432)
.start();
redisContainer = await new GenericContainer('redis:7-alpine')
.withExposedPorts(6379)
.start();
process.env.DATABASEURL = postgresql://postgres:test@localhost:${postgresContainer.getMappedPort(5432)}/test;
process.env.REDISURL = redis://localhost:${redisContainer.getMappedPort(6379)};
});
afterAll(async () => {
await postgresContainer.stop();
await redisContainer.stop();
});
// Vos tests ici
});
Ne JAMAIS stocker de credentials en clair dans les fichiers YAML. Utilisez les gestionnaires de secrets de la plateforme.
GitHub Actions :
- name: Deploy
env:
AWSACCESSKEYID: ${{ secrets.AWSACCESSKEYID }}
AWSSECRETACCESSKEY: ${{ secrets.AWSSECRETACCESSKEY }}
run: aws s3 sync ./dist s3://my-bucket
GitLab CI (avec Vault) :
variables:
VAULTADDR: https://vault.monentreprise.com
VAULTAUTHROLE: gitlab-ci
deploy:
idtokens:
VAULTIDTOKEN:
aud: https://vault.monentreprise.com
secrets:
DATABASEPASSWORD:
vault: production/database/password@secret
script:
- echo "Password: $DATABASEPASSWORD"
Séparez les stages de build et de déploiement pour une meilleure traçabilité.
# ❌ Mauvais : Tout ensemble
deploy:
script:
- npm run build
- docker build -t myapp .
- kubectl apply -f deployment.yaml
# ✅ Bon : Séparé et tracé
build:
script:
- npm run build
- docker build -t myapp:$CICOMMITSHA .
- docker push myapp:$CICOMMITSHA
deploy:
needs: [build]
script:
- kubectl set image deployment/myapp myapp=myapp:$CICOMMITSHA
Utilisez le SHA du commit pour une traçabilité complète.
# ✅ Excellent
docker build -t monapp:$(git rev-parse --short HEAD) .
docker build -t monapp:${CICOMMITSHA} .
# ⚠️ Acceptable (avec versioning)
docker build -t monapp:v1.2.3 .
# ❌ Mauvais : Impossible de tracer
docker build -t monapp:latest .
Erreur critique : Ne pas faire échouer le build sur des problèmes de tests ou de sécurité. "Rapide mais cassé" est pire que lent.
# ✅ Bon : Pipeline échoue si problème
security-scan:
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest
# ❌ Mauvais : Ignore les erreurs
security-scan:
script:
- trivy image myapp:latest || true # Continue même si vulnérabilités!
Implémentez des stratégies de déploiement avancées pour minimiser les risques.
Canary avec Kubernetes :
# Service principal (stable)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-stable
spec:
replicas: 9 # 90% du trafic
template:
metadata:
labels:
app: myapp
version: stable
---
# Deployment canary (nouvelle version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-canary
spec:
replicas: 1 # 10% du trafic
template:
metadata:
labels:
app: myapp
version: canary
---
# Service commun
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp # Route vers stable ET canary
ports:
- port: 80
Vos pipelines CI/CD font partie du code. Versionnez-les, reviewez-les, testez-les.
# Structure recommandée
.github/
workflows/
ci.yml # CI principal
deploy-staging.yml # Déploiement staging
deploy-prod.yml # Déploiement production
security.yml # Scans de sécurité
release.yml # Création de releases
.gitlab-ci/
templates/
build.yml
test.yml
deploy.yml
.gitlab-ci.yml # Fichier principal qui inclut les templates
Surveillez la santé de vos pipelines comme vous surveillez vos applications.
Métriques clés :
Exemple avec Prometheus :
# Exporter des métriques depuis GitHub Actions
name: Export metrics
run: |
echo "pipelinedurationseconds{job="$GITHUBJOB",status="${{ job.status }}"} $SECONDS" |
curl --data-binary @- http://pushgateway:9091/metrics/job/github-actions
Les tests sont de plus en plus optimisés par l'IA, qui sélectionne les tests pertinents basés sur les changements de code.
# Exemple avec AI Test Selection
test:
script:
- ai-test-selector --changed-files="$(git diff --name-only HEAD~1)"
--run-tests
# Exécute uniquement les tests affectés par les changements
Les équipes adoptent GitOps avec Argo CD ou Flux pour des déploiements déclaratifs.
# ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
spec:
source:
repoURL: https://github.com/monentreprise/myapp
targetRevision: HEAD
path: k8s/
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
Les équipes créent des "golden paths" : templates de pipelines réutilisables.
# Template réutilisable GitLab
# templates/nodejs-app.yml
.nodejs-app:
image: node:20-alpine
cache:
key: ${CICOMMITREFSLUG}
paths:
- nodemodules/
before_script:
- npm ci
Avant de déployer en production, vérifiez :
Tests :
Sécurité :
Déploiement :
Observabilité :
Qualité :
Le paysage CI/CD en 2025 offre des outils puissants et accessibles pour automatiser vos workflows de développement. Le choix entre GitHub Actions, GitLab CI et Jenkins dépend de votre contexte :
Principes universels :
L'avenir est aux pipelines intelligents (AI-driven), aux déploiements déclaratifs (GitOps) et aux plateformes d'ingénierie qui démocratisent les meilleures pratiques.
Article mis à jour en décembre 2025 avec les dernières tendances CI/CD et outils de l'écosystème DevOps.
Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.