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=8installe les paquets suivant :
composer require --dev phpunit/phpunit
composer require --dev phpstan/phpstan2. 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.gitEnsuite, 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.gitTout 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-receiveCe 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.