Aller au contenu principal

Automatisez le déploiement de vos projets avec Git (CI/CD)

Le déploiement manuel de vos projets en production est une perte de temps et un risque de tout casser.

Git permet d'automatiser tout cela. Avec un peu de configuration, chaque commit peut être déployé automatiquement sur votre serveur. Plus de manipulations manuelles, plus d'erreurs.

Dans cet article, nous vous montrons comment mettre en place un déploiement automatique avec Git, que vous utilisiez Git hooks, webhooks ou une plateforme CI/CD.
 

Tests automatiques : local et production

Le principe

Seuls les tags déclenchent un déploiement en production. Les commits simples sont de simples sauvegardes de code.

Tests en local (avant le push)

Avant de créer un tag, nous devons exécuter les tests car un test automatique sera lancé avant de pousser. Si le test échoue, le tag n'est pas poussé. Nous corrigeons le code et nous créons un nouveau tag.

Tests en production (après le push)

Quand le tag arrive sur le serveur, le déploiement se fait d'abord dans un dossier temporaire. Ensuite on copie les fichiers nécessaire vers le préprod. Les tests sont exécutés dans cet environnement (préprod). Si tout est OK, on copie du dossier temporaire vers le dossier de production. Si un test échoue, le dossier temporaire est supprimé et le site continue de fonctionner avec l'ancienne version.

Résultat

Le site actif n'est jamais touché pendant les tests. En cas d'erreur, rien n'est cassé.

 

Mettre en place les tests automatiques en local

1. Créer un Fichier "test" et le rendre executable chmod +x test.

Fichier test à la racine du projet :

#!/bin/bash
echo " Lancement des tests..."
vendor/bin/phpunit
vendor/bin/phpstan analyse src --level=8

installe les paquets suivant :

composer require --dev phpunit/phpunit
composer require --dev phpstan/phpstan

2. Bloquer le push d'un tag en cas d'échec 

Ajoutez dans .git/hooks/pre-push :

#!/bin/bash
# Lire les informations du push
while read local_ref local_sha remote_ref remote_sha
do
    # Vérifier si c'est un tag
    if [[ $remote_ref == refs/tags/* ]]; then
        echo "Tag détecté - Lancement des tests..."
        ./test
        if [ $? -ne 0 ]; then
            echo "Tests échoués - Push du tag annulé"
            exit 1
        fi
    fi
done

 

Préparation de l'environnement de production

Le principe

Sur le serveur de production, nous disposons de quatre zones distinctes :

  • Dossier Git : contient tous les commits et tags de votre dépôt
  • Dossier temporaire : reçoit les fichiers extraits du tag pour les tests immédiats
  • Dossier de préproduction : permet de faire des tests grandeur nature dans un environnement isolé
  • Dossier de production : contient la version actuellement accessible aux utilisateurs

Comment cela fonctionne :

Quand vous poussez un tag, les fichiers sont d'abord extraits dans le dossier temporaire. Après validation, ils sont copiés vers la préproduction pour des tests plus poussés. Si tout est conforme, la préproduction bascule en production. À chaque étape, le dossier de production reste intact jusqu'à la validation finale.

Exécution : ce que vous devez faire

Une seule manipulation manuelle est nécessaire : créer le dépôt nu sur votre serveur. ( Nous supposons que votre dossier de travail se somme app-mailer ).

git init --bare /home/git/app-mailer.git

Ensuite, ajoutez le script post-receive dans le dossier hooks de ce dépôt.

Vous devez ajouter le dépôt sur votre projet en local. (1x.1x.1x.x1:xxx est votre @IP et numéro de port)

git remote add prod [git@1x.1x.1x.x1:xxx]:/home/git/app-mailer.git

Tout le reste est automatisé : création des dossiers, extraction des fichiers, tests, bascule vers la production.

Le script post-receive

Le fichier post-receive se trouve dans le dossier /home/git/app-mailer.git/hooks/. Il s'exécute automatiquement après chaque push.

Voici sa structure générale ( à adapter selon vos besoins ) :

#!/bin/bash

# Configuration
GIT_DIR="/home/git/app-mailer.git"
DIR_BASE="/siteweb/app-mailer"
DIR_TEMP="$DIR_BASE/temp"
DIR_PREPROD="$DIR_BASE/preprod"
DIR_PROD="$DIR_BASE/prod"
BRANCH="1.x"

# Liste des fichiers/dossiers à copier ( cas de symfony 8 ).
FILES_TO_COPY=(
    "assets"
    "bin"
    "composer.json"
    "compose.yaml"
    "config"
    "package.json"
    "phpunit.dist.xml"
    "postcss.config.js"
    "public"
    "src"
    "scripts"
    "templates"
    "tests"
    "test"
    "translations"
    "webpack.config.js"
)

# Fonction pour copier les fichiers sélectivement
copy_files() {
    local source="$1"
    local dest="$2"
    for item in "${FILES_TO_COPY[@]}"; do
        if [ -e "$source/$item" ]; then
            echo "Copie de $item..."
            cp -r "$source/$item" "$dest/"
        fi
    done
}

# Fonction pour installer les dépendances
install_dependencies() {
    local target="$1"
    local env_type="$2"
    cd "$target"
    if [ ! -f "composer.lock" ]; then
        echo "❌ Erreur : composer.lock est manquant dans Git. Déploiement stoppé."
        exit 1
    fi
    if [ "$env_type" == "prod" ]; then
        echo "📥 Installation des dépendances de Production (--no-dev)..."
        composer install --no-interaction --optimize-autoloader --no-dev
        echo "🎨 Compilation des assets de Production (Tailwind v4 / Webpack)..."
        npm install
        npm run build -- --minify
    else
        echo "📥 Installation des dépendances de Développement..."
        composer install --no-interaction --optimize-autoloader
        echo "🎨 Compilation des assets de Développement (Tailwind v4 / Webpack)..."
        npm install
        npm run build
    fi
}

while read oldrev newrev ref
do
    # Vérifier que c'est un tag et que la branche est la bonne
    if [[ $ref == refs/tags/* || $ref == "refs/heads/$BRANCH" ]]; then
        echo "📦 Déploiement du tag $ref sur la branche $BRANCH"

        # 1. Créer les dossiers si inexistants
        mkdir -p "$DIR_TEMP"
        mkdir -p "$DIR_PREPROD"
        mkdir -p "$DIR_PROD"

        # 2. Extraire les fichiers du tag dans temp
        git --work-tree="$DIR_TEMP" --git-dir="$GIT_DIR" checkout -f "$ref"

        # 3. Copier de temp vers préproduction
        echo "Copie vers préproduction..."
        copy_files "$DIR_TEMP" "$DIR_PREPROD"

        # 4. Installation des dépendances dans préproduction
        install_dependencies "$DIR_PREPROD" "dev"

        # 5. action specifique lier à la preproduction
        if [ -f "$DIR_PREPROD/scripts/deploy-preprod.sh" ]; then
            bash "$DIR_PREPROD/scripts/deploy-preprod.sh"
        fi

        # 6. Exécution des tests dans préproduction
        cd "$DIR_PREPROD"
        chmod +x ./test
        ./test

        if [ $? -eq 0 ]; then
            echo "✅ Tests OK"
            if [[ $ref == refs/tags/* ]]; then
                echo "Déploiement vers production"
                # 7. Copier de temp vers production
                echo "Copie vers production..."
                copy_files "$DIR_TEMP" "$DIR_PROD"

                # 8. Installation des dépendances dans production
                install_dependencies "$DIR_PROD" "prod"

                # 9. action specifique lier à la production
                if [ -f "$DIR_PROD/scripts/deploy-prod.sh" ]; then
                    bash "$DIR_PROD/scripts/deploy-prod.sh"
                fi

                # 10. Nettoyer le dossier temporaire
                rm -rf "$DIR_TEMP"

                echo "🎉 Déploiement terminé"
            else
                echo "Déploiement uniquement sur préproduction"
                rm -rf "$DIR_TEMP"
            fi
        else
            echo "❌ Tests échoués - Déploiement annulé"
            rm -rf "$DIR_TEMP"
            exit 1
        fi
    fi
done

Rendre le script exécutable :

chmod +x /home/git/app-mailer.git/hooks/post-receive

Ce script peut fonctionner dans certains environnement, mais il peut arriver que le compte qui contient les données git soit différent de celui qui contient les données du projet. Dans ce cas utiliser la version suivante du fichier, car elle tient en compte les rôles. 

#!/bin/bash

# Configuration
GIT_DIR="/home/git/app-mailer.git"
DIR_BASE="/home/hbk_equipes/app-mailer"
DIR_TEMP="$DIR_BASE/temp"
DIR_PREPROD="$DIR_BASE/preprod"
DIR_PROD="$DIR_BASE/prod"
BRANCH="1.x"
PHP_BIN="/usr/bin/php8.4"
# user
GIT_USER="git"
DEPLOY_USER="hbk_equipes"  # Utilisateur propriétaire qui build
WEB_USER="www-data"        # Utilisateur du serveur web

# Liste des fichiers/dossiers à copier ( cas de symfony 8 ).
FILES_TO_COPY=(
    "assets"
    "bin"
    "composer.json"
    "composer.lock"
    "compose.yaml"
    "config"
    "package.json"
    "phpunit.dist.xml"
    "postcss.config.js"
    "public"
    "src"
    "scripts"
    "templates"
    "tests"
    "test"
    "translations"
    "webpack.config.js"
)

# Fonction pour copier les fichiers sélectivement
copy_files() {
    local source="$1"
    local dest="$2"
    # Ajustement temporaire pour permettre l'écriture au DEPLOY_USER si le dossier existe déjà
    if [ -d "$dest" ]; then
        sudo chown -R $DEPLOY_USER:$DEPLOY_USER "$dest"
        sudo chmod -R u+rwX "$dest"
    fi
    for item in "${FILES_TO_COPY[@]}"; do
        if [ -e "$source/$item" ]; then
            echo "Copie de $item..."
            if [ -d "$source/$item" ]; then
                sudo mkdir -p "$dest/$item"
                sudo rsync -a --delete "$source/$item/" "$dest/$item/"
            else
                sudo cp -f "$source/$item" "$dest/$item"
            fi
            # Droits initiaux pour le build
            sudo chown -R $DEPLOY_USER:$GIT_USER "$dest/$item"
            sudo chmod -R 775 "$dest/$item"
        fi
    done
}

# Fonction pour nettoyer le composer.json. Supprime les références locales de type "path" pour éviter les conflits de dépendances.
clean_composer_files() {
    local target_dir="$1"
    echo "🧼 Nettoyage du fichier composer.json dans $target_dir..."
    cd "$target_dir"
    # On vérifie si le script PHP est bien présent avant de l'exécuter
    if [ -f "scripts/clean-composer.php" ]; then
        sudo -u $DEPLOY_USER $PHP_BIN scripts/clean-composer.php
    else
        echo "⚠️ Attention : scripts/clean-composer.php introuvable !"
    fi
}

reinstall_packages() {
    local file="$1"
    local dev_flag="$2"
    local COMPOSER_PATH=$(which composer)
    if [ -f "$file" ] && [ -s "$file" ]; then
        PACKAGES=$(cat "$file")
        echo "📦 Réinstallation des packages: $PACKAGES"     
        sudo -u $DEPLOY_USER $PHP_BIN $COMPOSER_PATH require $dev_flag "$PACKAGES" --no-interaction 
        sudo rm -f "$file"       
    fi
}

# Fonction pour installer les dépendances et compiler les assets
install_dependencies() {
    local target="$1"
    local env_type="$2"
    cd "$target"
    if [ ! -f "composer.lock" ]; then
        echo "❌ Erreur : composer.lock est manquant dans Git. Déploiement stoppé."
        exit 1
    fi
    # Nettoyage des dépôts de type "path" avant le composer install
    clean_composer_files "$target"
    local COMPOSER_PATH=$(which composer)
    if [ "$env_type" == "prod" ]; then
        echo "📥 Installation des dépendances de Production"
        sudo -u $DEPLOY_USER $PHP_BIN $COMPOSER_PATH install --no-interaction --optimize-autoloader --no-dev
        reinstall_packages "composer_require.txt" ""
        #echo "🎨 Compilation des assets de Production (Tailwind v4 / Webpack)..."
        #sudo -u $DEPLOY_USER npm install
        #sudo -u $DEPLOY_USER npm run build -- --minify
    else
        echo "📥 Installation des dépendances de Développement (Preprod)..."
        sudo -u $DEPLOY_USER $PHP_BIN $COMPOSER_PATH install --no-interaction --optimize-autoloader
        reinstall_packages "composer_require.txt" ""
        reinstall_packages "composer_require_dev.txt" "--dev"
        #echo "🎨 Compilation des assets de Développement (Tailwind v4 / Webpack)..."
        #sudo -u $DEPLOY_USER npm install
        #sudo -u $DEPLOY_USER npm run build
    fi
}

while read oldrev newrev ref
do
    # Vérifier que c'est un tag et que la branche est la bonne.
    if [[ $ref == refs/tags/* || $ref == "refs/heads/$BRANCH" ]]; then
        echo "📦 Déploiement détecté pour la référence : $ref"
        if [ -d "$DIR_TEMP" ]; then
            sudo rm -rf "$DIR_TEMP"
        fi

        # 1. Initialisation sécurisée des répertoires
        for dir in "$DIR_TEMP" "$DIR_PREPROD" "$DIR_PROD"; do
            if [ ! -d "$dir" ]; then
                sudo mkdir -p "$dir"
                sudo chown $DEPLOY_USER:$GIT_USER "$dir"
                sudo chmod 2775 "$dir"
            fi
        done

        # 2. Extraction depuis Git vers TEMP (Droits accordés à DEPLOY_USER)
        sudo chown -R $DEPLOY_USER:$GIT_USER "$DIR_TEMP"
        git --work-tree="$DIR_TEMP" --git-dir="$GIT_DIR" checkout -f "$ref"

        # 3. Copier de temp vers préproduction
        echo "Copie vers préproduction..."
        copy_files "$DIR_TEMP" "$DIR_PREPROD"

        # 4. Installation des dépendances dans préproduction
        install_dependencies "$DIR_PREPROD" "dev"

        # 5. action specifique lier à la preproduction
        # if [ -f "$DIR_PREPROD/scripts/deploy-preprod.sh" ]; then
            #sudo -u $DEPLOY_USER bash "$DIR_PREPROD/scripts/deploy-preprod.sh"
            #if [ $? -ne 0 ]; then
            #    echo "❌ Échec du script deploy-preprod.sh - Déploiement stoppé"
            #    rm -rf "$DIR_TEMP"
            #    exit 1
            #fi
        # fi

        # 6. Exécution des tests dans préproduction
        cd "$DIR_PREPROD"
        sudo -u $DEPLOY_USER chmod +x ./test
        sudo -u $DEPLOY_USER ./test
        if [ $? -ne 0 ]; then
            echo "❌ Tests échoués en préproduction - Déploiement annulé."
            sudo rm -rf "$DIR_TEMP"
            exit 1
        fi
        echo "✅ Tests validés avec succès !"


        if [[ $ref == refs/tags/* ]]; then
            echo "🚀 [PROD] Feu vert ! Déploiement vers la production en cours..."
            # Ajustement final des permissions pour la PREPROD (Donne l'accès au serveur Web)
            sudo chown -R $WEB_USER:$DEPLOY_USER "$DIR_PREPROD"
            # 7. Copier de temp vers production
            echo "Copie vers production..."
            copy_files "$DIR_TEMP" "$DIR_PROD"

            # 8. Installation des dépendances dans production
            install_dependencies "$DIR_PROD" "prod"

            # 9. action specifique lier à la production
            # if [ -f "$DIR_PROD/scripts/deploy-prod.sh" ]; then
                #sudo -u $DEPLOY_USER bash "$DIR_PROD/scripts/deploy-prod.sh"
                #if [ $? -ne 0 ]; then
                #    echo "❌ Échec du script deploy-prod.sh"
                #fi
            # fi

            # Ajustement final des permissions pour la PRODUCTION (Sécurité WWW-DATA)
            echo "🔒 Ajustement final des permissions pour le serveur Web..."
            sudo chown -R $WEB_USER:$DEPLOY_USER "$DIR_PROD"

            # 10. Nettoyer le dossier temporaire
            sudo rm -rf "$DIR_TEMP"
            echo "🎉 APPLICATION DE PRODUCTION MISE À JOUR AVEC SUCCÈS"
        else
            echo "ℹ️ Déploiement terminé uniquement sur l'environnement de Préproduction."
            sudo rm -rf "$DIR_TEMP"
        fi
    fi
done


 

Conclusion

Et voilà le script final. Cela peut être fastidieux au début, mais c'est très important pour des projets qui évoluent.

La première mise en place est un peu complexe, mais la suite est simple comme bonjour : un commit et tout est en production.

Profile picture for user admin Stephane K

Écrit le

Il y'a 11 heures
Modifié
Il y'a 1 heure
Loading ...
Need personalized advice?
Envoyer