Aller au contenu
DevOps 7 min de lecture

CI/CD avec Jenkins et Docker : guide pratique

Mettre en place un pipeline CI/CD complet avec Jenkins et Docker : Jenkinsfile, étapes build/test/deploy, gestion des secrets et bonnes pratiques DevOps.

CI/CDJenkinsDockerDevOpsJava

Un pipeline CI/CD bien configuré est ce qui sépare une équipe qui livre avec confiance d’une équipe qui livre avec angoisse. Jenkins reste l’outil de référence dans de nombreuses entreprises, notamment sur des projets Java/AEM. Voici comment construire un pipeline solide avec Jenkins et Docker.

Pourquoi Docker dans le pipeline ?

Sans Docker, le pipeline dépend de l’environnement de l’agent Jenkins : version Java, Maven, Node installés sur la machine. Avec Docker, chaque étape tourne dans un conteneur isolé avec l’exact environnement dont elle a besoin. Résultat : builds reproductibles, zéro “ça marche sur ma machine”.

Structure d’un Jenkinsfile

Le pipeline est défini en code dans un Jenkinsfile à la racine du dépôt. C’est ce que Jenkins lit pour savoir quoi faire.

pipeline {
    agent none

    environment {
        APP_NAME    = 'mon-api'
        REGISTRY    = 'registry.monentreprise.com'
        IMAGE_TAG   = "${env.GIT_COMMIT[0..7]}"
    }

    stages {

        stage('Build & Test') {
            agent {
                docker {
                    image 'maven:3.9-eclipse-temurin-21'
                    args '-v $HOME/.m2:/root/.m2'
                }
            }
            steps {
                sh 'mvn clean verify -B -Dmaven.test.failure.ignore=false'
            }
            post {
                always {
                    junit 'target/surefire-reports/**/*.xml'
                    publishHTML(target: [
                        reportDir: 'target/site/jacoco',
                        reportFiles: 'index.html',
                        reportName: 'Couverture de tests'
                    ])
                }
            }
        }

        stage('Build Image Docker') {
            agent any
            when {
                branch 'main'
            }
            steps {
                script {
                    docker.build("${REGISTRY}/${APP_NAME}:${IMAGE_TAG}")
                }
            }
        }

        stage('Push Image') {
            agent any
            when {
                branch 'main'
            }
            steps {
                script {
                    docker.withRegistry("https://${REGISTRY}", 'registry-credentials') {
                        docker.image("${REGISTRY}/${APP_NAME}:${IMAGE_TAG}").push()
                        docker.image("${REGISTRY}/${APP_NAME}:${IMAGE_TAG}").push('latest')
                    }
                }
            }
        }

        stage('Deploy Staging') {
            agent any
            when {
                branch 'main'
            }
            steps {
                withCredentials([sshUserPrivateKey(
                    credentialsId: 'deploy-key',
                    keyFileVariable: 'SSH_KEY'
                )]) {
                    sh """
                        ssh -i ${SSH_KEY} deploy@staging.monentreprise.com \\
                        "docker pull ${REGISTRY}/${APP_NAME}:${IMAGE_TAG} && \\
                         docker stop mon-api || true && \\
                         docker rm mon-api || true && \\
                         docker run -d --name mon-api -p 8080:8080 \\
                           --env-file /etc/mon-api/env \\
                           ${REGISTRY}/${APP_NAME}:${IMAGE_TAG}"
                    """
                }
            }
        }
    }

    post {
        failure {
            emailext(
                subject: "Pipeline échoué : ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: "Voir les logs : ${env.BUILD_URL}",
                to: 'equipe@monentreprise.com'
            )
        }
    }
}

Gestion des secrets

Ne jamais mettre de secrets (mots de passe, tokens, clés SSH) dans le Jenkinsfile. Utilisez le gestionnaire de credentials Jenkins (Manage Jenkins > Credentials) et injectez-les avec withCredentials ou environment.

environment {
    DB_PASSWORD = credentials('db-password-prod')
}

Jenkins remplace automatiquement la valeur dans les logs par ****.

Cache Maven entre les builds

Sans cache, Maven retélécharge les dépendances à chaque build : lent et inutile. Le montage de volume -v $HOME/.m2:/root/.m2 persiste le cache local entre les runs sur le même agent.

Pour Jenkins distribué avec plusieurs agents, configurez un Nexus ou Artifactory comme proxy de dépendances. Tous les agents téléchargent depuis le cache réseau interne.

Dockerfile multi-étapes pour Java

# ─── Étape build ────────────────────────────
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B

# ─── Étape runtime ──────────────────────────
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=build /app/target/*.jar app.jar
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

Le build multi-étapes produit une image finale légère (JRE seulement, pas JDK) et sans les sources du projet.

Bonnes pratiques

Un Jenkinsfile par dépôt : le pipeline vit avec le code, versionné et révisable comme le reste.

Fail fast : les tests unitaires doivent tourner en premier. Si le build est cassé, inutile de construire l’image Docker.

Branches séparées : main → staging automatique. Déploiement en production sur approbation manuelle (input dans Jenkins).

Notifier l’équipe : un pipeline silencieux qui échoue sans alerter est un pipeline inutile.

Conclusion

Un pipeline CI/CD avec Jenkins et Docker n’est pas qu’une automatisation : c’est un filet de sécurité. Chaque commit passe par les mêmes étapes, dans les mêmes conditions. Les problèmes sont détectés tôt, les déploiements sont prévisibles, et l’équipe peut livrer sans risque.

Amine MEGDICHE

Amine MEGDICHE

Développeur AEM & Java Full Stack — Freelance depuis 2013

Vous avez un projet sur ces sujets ?

Envoyez-moi un message pour qualifier votre besoin, vos contraintes techniques et le bon format d'intervention.

Cadrer mon besoin